Menu

SQLite Backup & Restore: .backup, VACUUM INTO & API

So sicherst und stellst du eine SQLite-Datenbank zuverlässig wieder her – mit dem .backup-Befehl, VACUUM INTO und der Online-Backup-API. Plus: warum simples Datei-Kopieren ein Risiko ist.

Diese Seite enthält ausführbare Editoren — bearbeiten, ausführen und Ausgabe sofort sehen.

Warum ein einfaches cp nicht reicht

Eine SQLite-Datenbank ist eine einzelne Datei – da liegt es nahe, sie einfach mit einem Dateikopie-Befehl zu sichern. Manchmal klappt das. Oft eben nicht.

Zwei Dinge können dabei schiefgehen:

  • Eine andere Verbindung schreibt gerade, während du kopierst. Die Zieldatei enthält dann eine halb angewendete Transaktion – beim Öffnen sofort korrupt.
  • Die Datenbank läuft im WAL-Modus (Standard bei den meisten modernen Anwendungen). Die jüngsten Änderungen liegen in einer separaten Datei database.db-wal. Kopierst du nur die Hauptdatei, sind diese Daten still und heimlich verloren.

SQLite bringt für genau diesen Zweck passende Werkzeuge mit. Sie kümmern sich um Locks, WAL-Inhalte und parallele Schreibzugriffe, ohne dass du dir Sorgen machen musst. Greif lieber zu denen statt zu cp.

SQLite-Backup mit dem Befehl .backup

Der schnellste Weg, eine SQLite-Datenbank über die CLI zu sichern, ist der Punktbefehl .backup:

sqlite3 app.db
sqlite> .backup backup.db
sqlite> .quit

Damit landet eine vollständige Kopie von app.db in backup.db. Das funktioniert sogar dann, wenn parallel andere Prozesse lesend oder schreibend auf die Datenbank zugreifen – die Online-Backup-API arbeitet mit vielen kurzen Sperren statt einer einzigen großen, kopiert die Datenbank seitenweise und liest Seiten, die während des Vorgangs verändert wurden, einfach neu ein.

Heraus kommt eine voll funktionsfähige SQLite-Datenbank. Du kannst sie genauso öffnen wie jede andere:

sqlite3 backup.db
sqlite> .tables

Du kannst das Ganze auch in einem einzigen Shell-Befehl erledigen – und genau so sehen die meisten Cron-Jobs am Ende auch aus:

sqlite3 app.db ".backup '/var/backups/app-$(date +%Y%m%d).db'"

Eine Datei rein, eine Datei raus. Kein Dump-and-Restore-Umweg, kein SQL-Parsing – einfach Pages auf Storage-Ebene kopiert.

VACUUM INTO: kompakte Kopie der SQLite-Datenbank

VACUUM INTO ist ein verwandtes, aber anders gelagertes Werkzeug. Es schreibt eine frisch aufgebaute Kopie der Datenbank in eine neue Datei:

Das Ergebnis ist logisch dieselbe Datenbank, allerdings komplett neu geschrieben – jede Page dicht gepackt, keine Fragmentierung, keine übrig gebliebenen Free Pages aus gelöschten Zeilen. Dadurch wird die Backup-Datei so klein wie möglich.

Wann nimmst du was?

  • .backup – für regelmäßige, häufige Backups. Schneller, kommt gut mit gleichzeitigen Schreibzugriffen klar und liefert ein Byte-genaues Abbild.
  • VACUUM INTO – für periodische Snapshots, bei denen du zusätzlich eine aufgeräumte Datei in minimaler Größe willst. Langsamer, weil alles neu geschrieben wird, und die Quelldatenbank ist währenddessen mit einem Write Lock blockiert.

Beide Varianten erzeugen eine gültige .db-Datei, die du sofort öffnen kannst.

SQLite Online Backup API aus dem Anwendungscode nutzen

Innerhalb einer Anwendung rufst du natürlich nicht jedes Mal sqlite3 über die Shell auf. Stattdessen nutzt du die Online Backup API, die dein Treiber bereitstellt. In Pythons Standardbibliothek sqlite3 ist das Connection.backup:

import sqlite3

source = sqlite3.connect("app.db")
dest = sqlite3.connect("backup.db")

with dest:
    source.backup(dest)

source.close()
dest.close()

Die Methode backup kopiert Seiten von source nach dest, während andere Verbindungen ungestört weiterarbeiten. Optional kannst du pages= übergeben, um in Häppchen zu kopieren, und progress= für einen Callback — praktisch bei großen Datenbanken, wenn du den Kopiervorgang drosseln oder einen Fortschritt anzeigen willst.

Die meisten Treiber in anderen Sprachen stellen dieselbe C-API (sqlite3_backup_init, _step, _finish) unter ähnlichen Namen bereit. Das Muster ist immer gleich: Quelle öffnen, Ziel öffnen, seitenweise durchgehen, abschließen.

SQLite Hot Backup im laufenden Betrieb

Genau hier spielt SQLite seine Stärken aus. Sowohl .backup als auch die SQLite Online Backup API sind für Hot Backups gemacht — die Quell-Datenbank darf während des gesamten Vorgangs geöffnet und aktiv bleiben.

Was dabei tatsächlich passiert:

  1. Das Backup holt sich einen Shared Lock und beginnt, Seiten zu kopieren.
  2. Schreibt ein Writer auf eine Seite, die noch nicht kopiert wurde, bemerkt das Backup das und liest sie neu ein.
  3. Der Kopiervorgang ist fertig, sobald alle Seiten konsistent sind.

Du musst weder deine App stoppen noch Verbindungen rauswerfen oder Wartungsfenster einplanen. Bei einer stark genutzten Datenbank braucht das Backup vielleicht ein paar Extra-Runden, bis es konvergiert — aber konvergieren wird es. Die fertige Zieldatei ist ein konsistenter Snapshot zu einem bestimmten Zeitpunkt.

Ein Hinweis am Rande: Wenn du im WAL-Modus arbeitest, solltest du gelegentlich PRAGMA wal_checkpoint(TRUNCATE); ausführen, damit die WAL-Datei nicht ins Unermessliche wächst. Das Backup selbst geht korrekt mit dem WAL um — das ist einfach allgemeine WAL-Hygiene.

SQLite Datenbank wiederherstellen

Eine SQLite-Datenbank wiederherzustellen ist erfreulich unspektakulär — und genau das ist der Witz an der Sache. Die Backup-Datei ist bereits eine vollwertige Datenbank. Um sie zu nutzen, öffnest du sie einfach:

sqlite3 backup.db
sqlite> SELECT COUNT(*) FROM notes;

Wenn du eine laufende Datenbank wiederherstellen willst – etwa nach einem Datenverlust – ist diese Reihenfolge der sichere Weg:

  1. Beende alle Prozesse, die die Datenbank geöffnet haben.
  2. Lösche die vorhandenen Dateien app.db, app.db-wal und app.db-shm. Übrig gebliebene WAL-/SHM-Dateien aus der alten Datenbank bringen SQLite durcheinander, sobald die wiederhergestellte Hauptdatei daneben liegt.
  3. Spiele dein Backup ein: cp backup.db app.db.
  4. Starte deine Anwendung wieder.

Die -wal- und -shm-Dateien sind hier kein Detail, sondern entscheidend. Überspringst du Schritt 2, versucht SQLite unter Umständen, ein veraltetes WAL auf die frisch wiederhergestellte Hauptdatei anzuwenden – das Ergebnis ist dann entweder eine korrupte Datenbank oder ein wilder Mix aus alten und neuen Daten.

In der CLI gibt es übrigens auch den Befehl .restore, das Gegenstück zu .backup:

sqlite3 app.db
sqlite> .restore backup.db
sqlite> .quit

Damit wird der Inhalt der verbundenen Datenbank mit dem Inhalt von backup.db überschrieben. Im Hintergrund läuft dieselbe Online-Backup-API, nur in umgekehrter Richtung.

.dump ist ein anderes Werkzeug

In älteren Tutorials wirst du immer wieder über .dump stolpern. Das ist aber kein Backup im eigentlichen Sinne – stattdessen entsteht eine SQL-Textdatei mit CREATE- und INSERT-Anweisungen:

sqlite3 app.db .dump > app.sql

Zum Wiederherstellen spielst du das SQL einfach wieder ein:

sqlite3 new.db < app.sql

Praktisch ist das Ganze, wenn du zwischen SQLite-Versionen migrieren willst, Schemas per Git diffen möchtest oder die Daten in eine andere Datenbank-Engine überführen sollst. Allerdings ist diese Variante langsamer, größer und verlustanfälliger als .backup – eigene Collations, Generated Columns und einige Pragmas brauchen dann nochmal Extra-Aufmerksamkeit. Für ein echtes Backup einer produktiven Datenbank greifst du besser zu .backup oder VACUUM INTO.

Eine sinnvolle Backup-Routine für SQLite

Für die meisten Anwendungen hat sich diese Kombination bewährt:

  • Ein geplanter .backup-Lauf – stündlich, täglich, je nachdem, wie viel Datenverlust du verkraftest. Günstig, schnell und im laufenden Betrieb (Hot Backup) machbar.
  • Wöchentlich ein VACUUM INTO auf einen separaten Pfad. So fängst du schleichende Probleme ab, bekommst einen kompakten Snapshot und gehst dabei einen anderen Code-Pfad durch.
  • Eine Aufbewahrungsstrategie: Behalte die letzten N Tages-Backups und die letzten M Wochen-Backups. SQLite-Dateien lassen sich gut komprimieren, daher lohnt sich ein nachträgliches gzip backup.db auf jeden Fall.
  • Stelle ab und zu ein Backup tatsächlich wieder her und feuere ein paar Queries dagegen ab. Ein ungetestetes Backup ist nur eine Hoffnung – kein Backup.
# Täglich, per Cron:
sqlite3 /var/lib/app/app.db ".backup '/var/backups/app-$(date +%F).db'"
gzip "/var/backups/app-$(date +%F).db"

# Wöchentlich:
sqlite3 /var/lib/app/app.db "VACUUM INTO '/var/backups/app-weekly-$(date +%F).db'"

Beide Befehle kannst du gefahrlos ausführen, während deine App weiterhin Anfragen bedient.

Weiter geht's: PRAGMA-Einstellungen

Backups sind das eine Thema im Betrieb – das Feintuning des Laufzeitverhaltens das andere. SQLite bietet dafür die PRAGMA-Anweisungen: Journal-Modus, Synchronous-Level, Cache-Größe, Foreign-Key-Prüfung und mehr. Auf der nächsten Seite schauen wir uns die wichtigsten Stellschrauben in Ruhe an.

Häufig gestellte Fragen

Wie sichere ich eine SQLite-Datenbank?

In der CLI verbindest du dich mit der Quell-Datenbank und führst .backup pfad/zum/backup.db aus. Aus dem Anwendungscode heraus nimmst du die Online-Backup-API – also sqlite3_backup_init in C bzw. das Pendant im Treiber deiner Sprache. Beide Wege liefern eine konsistente Kopie, selbst wenn parallel andere Verbindungen schreiben.

Kann ich die .db-Datei nicht einfach kopieren?

Nur dann, wenn garantiert kein Prozess die Datenbank zum Schreiben geöffnet hat. Sonst erwischst du die Datei mitten in einer Transaktion und bekommst entweder ein korruptes Backup oder verlierst Daten, die noch im WAL-File liegen. Nimm stattdessen .backup oder VACUUM INTO – die kümmern sich sauber um Locking und WAL-Inhalte.

Was ist der Unterschied zwischen .backup und VACUUM INTO?

.backup läuft über die Online-Backup-API und erzeugt eine 1:1-Kopie auf Byte-Ebene, inklusive ungenutzter Pages. VACUUM INTO 'file.db' schreibt dagegen eine frisch kompaktierte Kopie – kleiner und defragmentiert, aber jede Page wird neu geschrieben. Für reguläre Backups nimmst du .backup, für VACUUM INTO entscheidest du dich, wenn du nebenbei auch Speicher freiräumen willst.

Wie stelle ich eine SQLite-Datenbank aus einem Backup wieder her?

Wenn das Backup eine .db-Datei ist, reicht es, sie zu öffnen – eine SQLite-Datenbank ist ja eine einzige Datei. Um eine bestehende Datenbank zu überschreiben, stoppst du die Anwendung, ersetzt die Datei (und löschst übrig gebliebene -wal/-shm-Dateien) und öffnest sie wieder. In der CLI kannst du alternativ .restore pfad/zum/backup.db ausführen, während du mit einer leeren Datenbank verbunden bist.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S