Beschädigte SQLite-Datenbank einer Android-App wiederherstellen

Android-Apps speichern ihren Zustand in SQLite, deshalb ist ein Absturz beim Start meist nur eine einzelne kaputte Seite oder ein kaputter Zeiger in einer .db-Datei — keine verlorenen Daten. Room und SQLiteOpenHelper schreiben eine standardmäßige SQLite-3-Datenbank, und die Zeilen liegen fast immer noch in ihren Seiten. Sie aus einer Kopie wiederherzustellen, die du vom Gerät gezogen hast, bedeutet nie, diese Datenbank an einen Server zu übergeben.

Deine Datei verlässt nie dein Gerät — die Reparatur läuft in deinem Browser. 0 Bytes hochgeladen.

Your data is the big block — usually intact. What breaks is the small index. Repair rebuilds it.

Drop a file here or browseTap to choose a file

ZIP · Office · PDF · video · JPG · PNG · RAR/7z · SQLite — repaired right here in your browser. Nothing is uploaded

0 bytes uploadedFree: 3 repairs/day — up to 500 MB video, 100 MB docs & archives, 50 MB photos. Free download. Single repair €5.90. No account required.

Jetzt reparieren

  1. Datei ablegen
  2. Reparatur läuft lokal
  3. Ergebnis herunterladen

Beide Persistenzschichten von Android schreiben gewöhnliches SQLite: Room erzeugt sein Schema auf derselben Engine, die auch das alte SQLiteOpenHelper nutzt, und beide legen eine einzige Datei unter /data/data/<paket>/databases/<name>.db ab. Wird diese Datei beschädigt, wirft Room android.database.sqlite.SQLiteDatabaseCorruptException: database disk image is malformed (code 11 SQLITE_CORRUPT) und die App wird zwangsweise beendet oder zeigt einen leeren Bildschirm. Zieh die vom Gerät geholte .db oben hinein — und füge ihre -wal-Begleitdatei im optionalen Feld hinzu, falls du sie hast — und das Tool scannt die Seiten, baut das Schema neu auf und fügt jede Zeile, die es noch dekodieren kann, in eine frische Datenbank ein, alles innerhalb deines Browser-Tabs. Die Datei wird niemals hochgeladen.

Wo eine Android-App ihre Datenbank ablegt — und was darin steckt

Auf jedem Android-Gerät liegt die Datenbank in der privaten Sandbox der App, unter /data/data/<paket>/databases/, normalerweise als drei Dateien: die Hauptdatei <name>.db, ein Write-Ahead-Log <name>.db-wal und ein Shared-Memory-Index <name>.db-shm. Die Hauptdatei beginnt mit der 16 Byte langen Signatur "SQLite format 3\000"; die Seitengröße als Zwei-Byte-Wert steht an Byte-Offset 16, und die Format-Lese-/Schreibversionsbytes an den Offsets 18 und 19 stehen auf 2, wenn die Datenbank im WAL-Modus läuft — den Room standardmäßig aktiviert.

Room fügt Kennzeichen hinzu, mit denen du bestätigen kannst, dass du die richtige Datei hast. Es legt eine Tabelle android_metadata mit einer einzigen Zeile für die Ländereinstellung an (zum Beispiel en_US) — diese Tabelle schreibt Androids Klasse SQLiteDatabase für jede App-Datenbank, ob mit Room oder ohne — sowie eine room_master_table mit den Spalten id und identity_hash, in der Room den Schema-Hash in der festen Zeile id = 42 speichert. Deine Entitätstabellen (mit @Entity annotiert) und ihre Indizes stehen daneben, alle aufgeführt in der Schematabelle sqlite_master (in neueren SQLite-Versionen unter dem Alias sqlite_schema). Eine App mit reinem SQLiteOpenHelper lässt die room_master_table weg, ist ansonsten aber dieselbe standardmäßige SQLite-Datei.

Wie die Datei 'malformed' wird — und die genauen Fehler

Eine SQLite-Datenbank ist ein flaches Feld aus Seiten fester Größe (standardmäßig 4096 Byte). Seite 1 trägt den Header und das Schema; jede Tabelle und jeder Index ist ein B-Baum, dessen innere Seiten Zellzeiger enthalten, die hinunter zu den Blattseiten führen, und die Blätter enthalten deine eigentlichen Datensätze. Die Engine liest den Header, findet das Schema in sqlite_master und folgt dann diesen Zeigern bis zu den Zeilen.

Die Zeiger sind die Schwachstelle. Wenn der App-Prozess mitten in einem Schreibvorgang vom Low-Memory-Killer von Android beendet wird, dem Telefon der Strom ausgeht, der Speicher volläuft oder zwei Prozesse dieselbe Datei ohne korrekte Sperrung öffnen, kann eine einzelne innere Seite oder ein Header-Feld inkonsistent zurückbleiben. SQLite hält dann beim ersten Problem an und weist die gesamte Datei mit database disk image is malformed zurück — Ergebniscode 11 (SQLITE_CORRUPT) — obwohl die Blattseiten hinter der Bruchstelle weiterhin intakte, selbstbeschreibende Datensätze enthalten. Ein verwandter Fehler, file is not a database — Ergebniscode 26 (SQLITE_NOTADB) — bedeutet, dass die 16 Byte lange Signatur des Headers falsch ist: Entweder wurden diese Header-Bytes beschädigt (die Seiten dahinter sind oft in Ordnung und wiederherstellbar) oder die Datei ist tatsächlich etwas anderes, etwa eine mit SQLCipher verschlüsselte Datenbank, die sich als hochentropisches Rauschen liest.

Eine Android-spezifische Falle macht das dringlich: Der DefaultDatabaseErrorHandler.onCorruption() des Frameworks löscht die Datenbankdatei, wenn er beim Öffnen eine Beschädigung erkennt. Eine App, die auf den Fehler "malformed" gestoßen ist, kann die Datei also bei ihrem nächsten Start bereits gelöscht haben. Kopiere die Datenbank vom Gerät herunter — und arbeite mit dieser Kopie — bevor du die App erneut öffnest.

Die Begleitdateien -wal und -shm: zieh alle drei

Room aktiviert das Write-Ahead-Logging standardmäßig (enableWriteAheadLogging()), was bedeutet, dass zuletzt bestätigte Änderungen vorläufig in der Geschwisterdatei <name>.db-wal abgelegt werden, bis ein Checkpoint sie zurück in die Haupt-.db überträgt. Nach einem Absturz können deine neuesten Zeilen nur in diesem WAL liegen. Ziehst du die Hauptdatei ohne es, fehlt der wiederhergestellten Datenbank alles, was seit dem letzten Checkpoint geschrieben wurde — ein häufiger Grund, warum Leute glauben, "die Hälfte meiner Daten ist weg".

Das WAL ist nicht die Hauptdatenbank: Es beginnt mit einem 32 Byte langen Header, dessen magische Zahl 0x377f0682 oder 0x377f0683 ist (das niederwertige Bit wählt Frame-Prüfsummen im Big-Endian- oder Little-Endian-Format), gefolgt von Frames, die jeweils aus einem 24 Byte langen Frame-Header plus einem Seitenabbild bestehen. Die -shm-Datei ist nur ein Shared-Memory-Index in das WAL; sie wird automatisch neu erzeugt und kann bedenkenlos zurückgelassen werden. Zieh also die Haupt-.db und die -wal zusammen: Leg die Datenbank oben ab und füge dann die -wal im optionalen Begleitdatei-Feld hinzu; ihre bestätigten Frames werden vor dem Scan überlagert, sodass die Wiederherstellung die letzte bestätigte Transaktion statt des letzten Checkpoints widerspiegelt.

Die Datenbank sicher vom Gerät holen

Bei einem debugfähigen Build erreichst du den privaten Ordner mit run-as: zum Beispiel adb exec-out run-as <paket> tar c ./databases | tar xv, oder kopiere die Dateien mit run-as <paket> cp databases/<name>.db /sdcard/ in den gemeinsamen Speicher und dann adb pull. Release-Apps (nicht debugfähig) verweigern run-as, du brauchst also ein gerootetes Gerät oder die app-eigene Exportfunktion. Der alte Weg adb backup -f backup.ab <paket> wurde in Android 12 abgekündigt und respektiert android:allowBackup, weshalb er dafür unzuverlässig ist. Welchen Weg du auch wählst, hol dir <name>.db, <name>.db-wal und <name>.db-shm in einem Zug, damit das WAL zur Hauptdatei passt.

Mit der Kopie auf deinem Rechner ist die klassische Prüfung sqlite3 name.db "PRAGMA integrity_check;" (oder das schnellere quick_check), und die klassische Reparatur ist SQLites eigener Befehl .recover, der jede dekodierbare Zeile ausliest und in eine brandneue Datei schreibt. Genau das macht diese Seite im Browser: Sie geht die Seiten durch, baut die B-Bäume neu auf, greift für Zeilen, die die Zeigerkette nicht mehr erreicht, auf einen rohen Seiten-Scan zurück und legt alles, was sie keiner bekannten Tabelle zuordnen kann, in eine Tabelle lost_and_found, statt es zu verwerfen — so wird nichts weggeworfen und nichts hochgeladen.

Was sich reparieren lässt und was nicht

Kann repariert werden

  • "database disk image is malformed (code 11 SQLITE_CORRUPT)" durch eine beschädigte Seite oder einen beschädigten B-Baum-Zellzeiger, mit intakten Blattseiten dahinter
  • Ein falscher oder beschädigter Header (fehlerhaftes Seitengrößen- oder Seitenzahl-Feld), wenn die nachfolgenden Seiten noch lesbar sind
  • Zeilen auf Seiten, die die Zeigerkette nicht mehr erreicht, durch einen rohen Seiten-Scan in eine Tabelle lost_and_found geholt
  • Zeilen ohne Checkpoint aus einer bereitgestellten Begleitdatei .db-wal (die neuesten Daten nach einem Absturz)
  • Room- und SQLiteOpenHelper-Datenbanken, die vom Gerät kopiert wurden, bevor der Fehlerhandler der App sie löschte

Kann nicht repariert werden

  • Zeilen, die physisch überschrieben oder am Dateiende abgeschnitten wurden — diese Bytes existieren nicht mehr
  • Mit SQLCipher verschlüsselte Datenbanken (Signal oder Room-Apps mit einer SupportFactory) ohne die Passphrase — die Seiten sind Chiffretext
  • Eine Datenbank, die DefaultDatabaseErrorHandler bereits gelöscht hat — das ist zunächst ein Problem der Datenrettung auf dem Gerät
  • Die exakten Spaltenaffinitäten: Werte kommen als ihre Speicherklasse auf der Festplatte zurück (NULL/INTEGER/REAL/TEXT/BLOB)
  • Eine .db-Datei mit 0 Byte — es gibt nichts auf der Festplatte zu dekodieren

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.

Häufige Fragen

Meine App hat nach dem Absturz ihre eigene Datenbank gelöscht — warum?

Androids DefaultDatabaseErrorHandler.onCorruption() löscht die Datei, wenn er beim Öffnen SQLITE_CORRUPT erkennt, ein zweiter Start kann sie also entfernen. Deshalb besteht die Lösung darin, die .db (und ihre -wal) zuerst mit adb run-as vom Gerät zu kopieren und aus der Kopie wiederherzustellen — niemals aus der Live-Datei, die die App weiterhin öffnet.

Ich habe die .db gezogen, aber es fehlen aktuelle Daten. Was ist passiert?

Room läuft standardmäßig im WAL-Modus, die neuesten bestätigten Zeilen liegen also in <name>.db-wal und wurden noch nicht in die Hauptdatei übertragen. Zieh alle drei Dateien — .db, -wal und -shm — und füge die -wal im optionalen Begleitdatei-Feld hinzu; ihre bestätigten Frames werden vor dem Scan überlagert, damit die letzte Transaktion enthalten ist.

Funktioniert das mit einer Room-Datenbank oder nur mit rohem SQLite?

Mit beiden — Room ist im Kern ganz normales SQLite. Die Wiederherstellung baut android_metadata, room_master_table (einschließlich des in id = 42 gespeicherten identity_hash) und deine @Entity-Tabellen neu auf. Kommt der Schema-Hash intakt durch, öffnet Room die neu aufgebaute Datei wieder, ohne zu beklagen, dass es die Datenintegrität nicht überprüfen kann.

Bei mir lautet der Fehler stattdessen 'file is not a database'. Ist das wiederherstellbar?

Manchmal. Das ist SQLITE_NOTADB (Code 26): Die 16 Byte lange Signatur des Headers ist falsch. Wurden nur die Header-Bytes beschädigt, sind die Seiten dahinter meist intakt und wiederherstellbar. Ist die ganze Datei hochentropisches Rauschen, handelt es sich um eine verschlüsselte Datenbank (SQLCipher) oder gar keine Datenbank — das lässt sich ohne den Schlüssel nicht wiederherstellen. Die Diagnose unterscheidet beide Fälle.

Wird meine Datenbank zum Reparieren hochgeladen?

Nein. Die Datenbank einer App ist die Datei, die du am wenigsten auf dem Server eines anderen haben möchtest — sie kann jeden Benutzerdatensatz, jede Nachricht und jedes Token enthalten, das die App gespeichert hat. Hier wird die .db von deiner Festplatte gelesen und in deinem Browser neu aufgebaut; du kannst den Netzwerk-Tab beobachten und bestätigen, dass 0 Byte das Gerät verlassen.

Verwandt: SQLite-Datenbank reparieren (beliebige Quelle) · Android-Video reparieren · Excel-Arbeitsmappe reparieren · Das Zero-Upload-Versprechen überprüfen