Jetzt reparieren
- Datei ablegen
- Reparatur läuft lokal
- Ergebnis herunterladen
Wenn SQLite — oder irgendetwas, das darauf aufbaut: der Verlauf eines Browsers, das Backup eines Smartphones, die lokale Datenbank einer Messaging-App, die Einstellungen einer Desktop-Anwendung — "database disk image is malformed" meldet, ist es auf SQLITE_CORRUPT (Ergebniscode 11) gestoßen. Es durchlief gerade den B-Tree, der deine Tabellen indiziert, und landete auf einer Seite, deren Typ-Byte, Zellzeiger oder Datensatzkopf dem Format widersprechen, also hält es an, statt Datenmüll zurückzugeben. Lege die .db-, .sqlite- oder .sqlite3-Datei oben ab und das Werkzeug liest sie Seite für Seite in deinem Browser, folgt der B-Tree-Struktur, die überlebt hat, durchkämmt den Rest mit einem zeigerfreien Scan und schreibt jede dekodierbare Zeile in eine brandneue, öffenbare Datenbank — ohne dass die Datei jemals dein Gerät verlässt.
Was "malformed" auf Seitenebene bedeutet
Eine SQLite-Datenbank ist eine einzige Datei, zerlegt in Seiten fester Größe — eine Zweierpotenz zwischen 512 und 65536 Bytes, gespeichert als 16-Bit-Wert am Offset 16 des Headers (der auf der Festplatte stehende Wert 1 ist die Art und Weise, wie die Spezifikation 65536 darstellt). Jede Datei beginnt mit der 16 Byte langen magischen Zeichenkette SQLite format 3\0, gefolgt von einem 100 Byte langen Header, der die Seitengröße festhält, die Textkodierung am Offset 56 (1 = UTF-8, 2 = UTF-16le, 3 = UTF-16be), die im Header angegebene Seitenzahl am Offset 28 und die Freiliste (freelist). Wird die Seitengröße falsch gelesen, landet jede Seite nach der ersten am falschen Offset — und das ist eine der Arten, wie eine Datei "malformed" wird, ohne dass eine einzige Zeile angetastet wird.
Im Inneren leben die Daten in B-Trees. Jede Seite beginnt mit einem Typ-Byte: 0x0d für ein Tabellenblatt (das die eigentlichen Zeilen enthält), 0x05 für eine innere Tabellenseite (die nur Zeiger auf Kindseiten enthält) und 0x0a/0x02 für die Index-Entsprechungen. Seite 1 ist besonders — ihr B-Tree-Header steht nach dem 100 Byte langen Datenbank-Header — und sie ist die Wurzel von sqlite_master, der Schema-Tabelle, deren Zeilen (type, name, tbl_name, rootpage, sql) lauten. Um eine deiner Tabellen zu lesen, schlägt SQLite dort ihre rootpage nach, steigt dann über die inneren Seiten bis zu den Blättern hinab und liest aus jeder Zelle die Nutzlastlänge (varint), die rowid (varint) und den Datensatzrumpf.
Da eine Abfrage diesen Baum durchläuft, vergiftet ein einziger kaputter Verweis alles darunter. Eine auf Null gesetzte innere Seite, ein Zellzeiger, der über das Ende der Seite hinaus zeigt, ein Zeiger auf das rechte Ende, der eine Schleife bildet, oder ein Datensatzkopf, dessen Serientypen nicht aufgehen — jedes davon lässt SQLite "database disk image is malformed" auslösen, sobald es auf den Schaden tritt. Die Zeilen auf den gesunden Blattseiten sind unversehrt; SQLite kann sie nur über einen kaputten Index nicht mehr erreichen. PRAGMA integrity_check meldet dieselbe Fehlerklasse.
Wie ein roher Seitendurchlauf deine Zeilen rettet
Die Lösung ahmt nach, was der eigene .recover-Befehl der SQLite-Shell tut, läuft aber vollständig im Browser. Zuerst liest das Werkzeug sqlite_master von Seite 1, um den Namen jeder Tabelle, ihre Spalten (aus dem SQL ihres CREATE TABLE geparst) und ihre rootpage zu erfahren, und durchläuft dann jeden B-Tree von dieser Wurzel aus — der schnelle, exakte Weg, wenn die Zeiger intakt sind. Trifft ein Durchlauf auf eine fehlende oder ungültige Seite, stuft er auf "unvollständig" herab, statt abzubrechen, und behält jede Zeile, die er bis zum Bruch gesammelt hat.
Dann kommt der Teil, der ein bloßes erneutes Öffnen übertrifft: ein zeigerfreier Durchgang. Der Scanner ignoriert alle Zeiger und behandelt jede Tabellenblattseite (Typ 0x0d) der Datei als einen Sack voller Datensätze und dekodiert jede Zelle direkt aus ihren Bytes — die Header-Länge (varint), das Serientyp-Array und dann jeden Wert (Serientyp 0 ist NULL, 8/9 sind die Literale 0 und 1, 7 ist ein float64, und gerade/ungerade Codes über 12 sind BLOB/TEXT). Die Overflow-Ketten großer Werte werden von Seite zu Seite verfolgt. Genau so überleben Zeilen eine auf Null gesetzte innere Seite, eine Seite in der Freiliste oder eine gelöschte Tabelle: Das Blatt enthält die Daten weiterhin, auch wenn nichts mehr darauf zeigt. Zeilen, deren Spaltenzahl eindeutig zu einer bekannten Tabelle passt, werden wieder in sie eingefügt; die wirklich nicht zuordenbaren werden in eine Tabelle lost_and_found mit den Spalten pgno, cellidx, nfield, rowid und c0…cN geschrieben, sodass nichts Dekodierbares verworfen wird.
Zuletzt baut das Werkzeug eine neue Datenbank mit sqlite3.wasm (vom selben Origin unter /engines/sqlite/ geladen) und gibt für jede Tabelle ein nachsichtiges CREATE TABLE aus — die ursprünglichen Spaltennamen und jeder INTEGER PRIMARY KEY bleiben erhalten, damit der rowid-Alias erhalten bleibt, aber ohne NOT NULL/UNIQUE/CHECK/Fremdschlüssel-Einschränkungen, die eine wiederbelebte Zeile ablehnen würden — und führt dann für jede wiederhergestellte Zeile ein INSERT OR IGNORE aus und exportiert das Ergebnis als saubere .db. Wird eine benachbarte -wal-Datei (Write-Ahead-Log) bereitgestellt, werden zunächst ihre bestätigten Frames überlagert, sodass die Wiederherstellung die letzte bestätigte Transaktion widerspiegelt.
Wo die Daten wirklich enden — und warum nichts hochgeladen wird
Die Wiederherstellung erreicht, was vorhanden ist; sie kann nicht erfinden, was die Festplatte nicht mehr enthält. Wurde die Datei abgeschnitten (truncated) — eine Kopie oder Synchronisierung, die zu früh anhielt und eine Größe hinterließ, die kein exaktes Vielfaches der Seitengröße ist — so ist jede Seite nach dem Schnitt physisch verschwunden, und du erhältst die Zeilen, die davor vollständig geschrieben wurden. Zellen, die auf intakten Seiten liegen, deren Datensatzkopf aber in sich widersprüchlich ist (der klassische Bit-Rot), werden verworfen und gezählt, statt erraten zu werden. Eine verschlüsselte Datenbank (SQLCipher oder die SEE-Erweiterung) hat keine magische Zeichenkette im Klartext — ihre erste Seite ist Chiffretext — also gibt es ohne den Schlüssel nichts zu durchlaufen, und eine Wiederherstellung wird nicht versucht.
Zwei Ehrlichkeitshinweise, auf denen SQLite selbst besteht. Gelöschte Zeilen können wieder auftauchen: Der zeigerfreie Scan liest Seiten aus der Freiliste und noch nicht kompaktierte (vacuum) Seiten, sodass Datensätze, die du für verschwunden hieltest, im Ergebnis wieder auftauchen können. Und die wiederhergestellten Werte spiegeln die Speicherklassen auf der Festplatte wider, nicht die deklarierten Spaltenaffinitäten — der scheinbare Typ einer Spalte kann sich verschieben. Die eigene Anleitung von SQLite ist unmissverständlich: Daten, die aus einer beschädigten Datenbank gezogen werden, sind immer verdächtig. Prüfe sie gegen eine bekannt gute Quelle, bevor du dich darauf verlässt.
All das läuft im Tab deines Browsers — der Scanner ist abhängigkeitsfreies TypeScript und der Schreiber ist WASM, es gibt also keinen Umweg über einen Server. Das zählt, wenn die Datenbank ein Passwort-Tresor, ein Chatverlauf, Gesundheitsdaten oder der private Speicher einer App ist: Du kannst den Netzwerk-Tab öffnen und bestätigen, dass 0 Bytes der Datei jemals dein Gerät verlassen.
Was sich reparieren lässt und was nicht
Kann repariert werden
- Datenbanken, die SQLITE_CORRUPT / "database disk image is malformed" wegen einer beschädigten B-Tree-Seite oder eines beschädigten Zeigers auslösen
- Zeilen auf gesunden Tabellenblattseiten, die eine kaputte innere Seite oder eine defekte rootpage unerreichbar gemacht hat
- Zeilen auf auf Null gesetzten inneren Seiten, Seiten in der Freiliste oder gelöschten Tabellen, wiederhergestellt durch den zeigerfreien Blattscan
- Große Werte, die auf Ketten von Overflow-Seiten übergelaufen sind, während des Durchlaufs wieder zusammengesetzt
- Nicht per Checkpoint gesicherte Änderungen in einer benachbarten -wal-Datei, vor dem Scannen überlagert
Kann nicht repariert werden
- Zeilen nach dem Schnitt in einer abgeschnittenen Datei — diese Seiten sind physisch verschwunden
- Zellen, deren Datensatzkopf zu stark beschädigt ist, um dekodiert zu werden (Bit-Rot) — sie werden verworfen, nicht erraten
- Verschlüsselte SQLCipher-/SEE-Datenbanken ohne den Schlüssel (die Seiten sind Chiffretext)
- Die ursprünglichen UNIQUE-/CHECK-/Fremdschlüssel-Garantien — die Wiederherstellung lockert die Einschränkungen, damit wiederbelebte Zeilen nicht abgelehnt werden
- Eine Datei mit 0 Bytes oder ohne SQLite-Magie und ohne dekodierbare Blattseiten
Wenn eine Reparatur fehlschlägt, sagen wir dir warum (fehlende Daten gegenüber beschädigter Struktur), und für eine fehlgeschlagene Reparatur wird dir nie etwas berechnet.