Jetzt reparieren
- Datei ablegen
- Reparatur läuft lokal
- Ergebnis herunterladen
Der standardmäßige und empfohlene Typ des persistenten Speichers von Core Data ist NSSQLiteStoreType: eine normale SQLite-3-Datenbank auf der Festplatte, üblicherweise <AppName>.sqlite genannt, im Library-Verzeichnis der App. Wenn sie sich nicht öffnen lässt, wirft der Store-Coordinator einen NSCocoaErrorDomain-Fehler und SQLite bezeichnet die Datei als fehlerhaft (malformed), aber eine fehlerhafte Datenbank ist selten eine leere: Die Zeilen liegen noch in ihren Seiten, hinter einem kaputten Index. Zieh die .sqlite oben hinein — und füge ihre -wal-Begleitdatei hinzu, falls du sie hast — und das Werkzeug liest die Datei in deinem Browser, baut das Schema neu auf und fügt jede Zeile, die es noch decodieren kann, in einen frischen, öffenbaren Speicher wieder ein. Es wird nichts installiert und die Datenbank wird nie hochgeladen.
Was Core Data tatsächlich auf die Festplatte schreibt
Core Data ist ein Framework für Objektgraphen und Persistenz, keine Datenbank — die Datenbank ist SQLite, und Core Data bestimmt ihr Layout. Öffne einen Core-Data-Speicher in einem beliebigen SQLite-Browser, und du wirst keine freundlichen Tabellennamen sehen; du siehst ein verfremdetes Schema, das das Framework erzeugt. Jede Entität wird zu einer Tabelle namens Z + der großgeschriebene Entitätsname, eine Person-Entität ist also die Tabelle ZPERSON. Jede Zeile trägt drei Systemspalten: Z_PK (der ganzzahlige Primärschlüssel), Z_ENT (welche Entität/Unterklasse die Zeile ist) und Z_OPT (ein Versionszähler für optimistisches Sperren). Deine Attribute bekommen ebenfalls ein Präfix — aus firstName wird ZFIRSTNAME — und eine To-One-Beziehung wird als Fremdschlüsselspalte gespeichert, die den Z_PK der Zielzeile enthält.
Neben deinen Daten stehen drei Verwaltungstabellen. Z_PRIMARYKEY enthält eine Zeile pro Entität mit den Spalten Z_ENT, Z_NAME, Z_SUPER und Z_MAX — dem höchsten bisher vergebenen Primärschlüssel, den Core Data hochzählt, um die nächste Einfügung zuzuweisen. Z_METADATA enthält Z_VERSION, Z_UUID und Z_PLIST, ein Binär-Plist-Blob mit den Metadaten des Speichers, darunter NSStoreModelVersionHashes und die Store-UUID. Z_MODELCACHE speichert das kompilierte Modell zwischen. Datumsangaben werden als REAL-Wert von Sekunden seit dem Cocoa-Referenzdatum gespeichert, 2001-01-01 00:00:00 UTC — nicht der Unix-Epoche — sodass ein Rohzeitstempel von 0 der 1. Januar 2001 ist.
All das lebt in einem standardmäßigen SQLite-Container: einem flachen Array von Seiten fester Größe (standardmäßig 4096 Bytes), wobei die ersten 16 Bytes der Datei die magische Zeichenkette SQLite format 3\000 sind. Seite 1 enthält den Datei-Header und das sqlite_master-Schema; jede Z-Tabelle ist ein B-Baum, dessen innere Seiten nach unten auf Blattseiten verweisen, welche die eigentlichen Datensätze enthalten. Zerstöre einen einzigen Zeiger, und SQLite verweigert die ganze Datei — obwohl die mit Zeilen gefüllten Blattseiten unberührt sind.
Die Dateien -wal und -shm: welche deine Daten enthält
Seit iOS 7 (und OS X 10.9 Mavericks) öffnet Core Data seinen Speicher standardmäßig im WAL-Modus — Write-Ahead-Logging, also vorausschauendes Protokollieren. Deshalb ist ein einzelner Speicher in Wirklichkeit bis zu drei Dateien auf der Festplatte: <AppName>.sqlite, <AppName>.sqlite-wal und <AppName>.sqlite-shm. Den Unterschied zu verstehen entscheidet darüber, ob deine neuesten Daten überleben.
Die -wal-Datei ist das Write-Ahead-Log. Wenn deine App eine Transaktion bestätigt, werden die geänderten Seiten als Frames an die -wal angehängt, statt direkt in die Haupt-.sqlite geschrieben zu werden; erst ein regelmäßiger Checkpoint faltet sie zurück in die Datenbank. Die Folge: Wenn die App abgestürzt oder vor einem Checkpoint hart beendet wurde, können die frischesten bestätigten Zeilen nur in der -wal liegen. Ihr Aufbau ist genau festgelegt — ein 32 Byte großer Header (magische Zahl 0x377f0682 oder 0x377f0683, eine Formatversion, die Seitengröße, eine Checkpoint-Sequenz, zwei Salt-Werte und eine Prüfsumme), gefolgt von Frames, jeder mit einem 24 Byte großen Frame-Header (Seitennummer, die Datenbankgröße in Seiten bei einem Commit-Frame oder sonst 0, die beiden Salts und zwei Prüfsummen) plus einer Datenseite.
Die -shm-Datei ist der Shared-Memory-WAL-Index (wal-index): eine Nachschlagestruktur, mit der SQLite Seiten innerhalb der -wal schnell findet. Sie besteht vollständig aus abgeleiteten Daten — sie enthält keine deiner Zeilen, und SQLite baut sie automatisch aus der -wal neu auf. Die Regel ist also einfach: bring die -wal mit, ignoriere die -shm. Zieh zuerst die .sqlite hinein und füge dann die -wal in den optionalen Begleitdatei-Slot ein: Ihre bestätigten Frames werden vor dem Scan überlagert, sodass die Wiederherstellung die letzte bestätigte Transaktion widerspiegelt statt des letzten Checkpoints. Nur die .sqlite von einem Gerät zu kopieren und die -wal zurückzulassen ist der klassische Weg, um stillschweigend aktuelle Daten zu verlieren — eine Falle sowohl für die Wiederherstellung nach einem Absturz als auch für die forensische Extraktion.
Die Fehler, die du siehst, und was die Zeilenrettung wiederherstellt
Auf der SQLite-Ebene scheitert der Speicher mit einem von zwei Ergebniscodes. SQLITE_CORRUPT (Code 11) gibt "database disk image is malformed" aus ("das Datenträgerabbild der Datenbank ist fehlerhaft"): Die Engine folgte einem Zeiger und fand etwas, das keine gültige B-Baum-Seite ist — eine mit Nullen gefüllte Seite, ein falscher Zellenoffset, eine Seitenzahl, die nicht zur Dateilänge passt. SQLITE_NOTADB (Code 26) gibt "file is not a database" aus ("Datei ist keine Datenbank"): Die 16 magischen Header-Bytes sind falsch, was bedeutet, dass entweder die Header-Bytes beschädigt wurden (die dahinter liegenden Seiten sind oft in Ordnung) oder die Datei wirklich etwas anderes oder verschlüsselt ist. Core Data verpackt das und präsentiert NSCocoaErrorDomain Code 259 (NSFileReadCorruptFileError) — "The file couldn't be opened because it isn't in the correct format." ("Die Datei konnte nicht geöffnet werden, weil sie nicht das richtige Format hat.") — während der rohe SQLite-Code im userInfo des Fehlers unter dem Schlüssel NSSQLiteErrorDomain steckt, sodass eine Log-Zeile mit NSSQLiteErrorDomain=11 deine Bestätigung ist, dass es sich um eine Beschädigung auf Seitenebene handelt und nicht um eine falsche Schemaversion.
Die Wiederherstellung versucht nicht, die kaputte Datei an Ort und Stelle zu reparieren. Sie liest alles noch Decodierbare heraus und schreibt es in eine brandneue Datenbank — dieselbe Strategie wie SQLites eigener .recover-Befehl. Sie durchläuft den B-Baum jeder Z-Tabelle und decodiert die Blattdatensätze; für Seiten, welche die Zeigerkette nicht mehr erreicht, greift sie auf einen rohen Seiten-Sweep zurück und fängt selbstbeschreibende Datensätze direkt aus der Datei ab. Zeilen, die sich keiner bekannten Tabelle zuordnen lassen, landen in einer lost_and_found-Tabelle, statt verworfen zu werden, und die Z_MAX-Werte von Z_PRIMARYKEY werden aus den wiederhergestellten Zeilen neu aufgebaut, damit die App weiter einfügen kann, ohne Primärschlüssel-Kollisionen. Da der Sweep auch Freelist-Seiten (Freispeicherliste) und nicht vakuumierte Seiten liest, können Zeilen, die deine App früher gelöscht hat, wieder auftauchen — nützlich, aber gut zu wissen.
Externe Binärdaten leben außerhalb des Speichers
Eine ehrliche Einschränkung ist spezifisch für Core Data. Wenn bei einem Binärattribut (einem Foto, einem PDF, Audio) "Allows External Storage" ("externe Speicherung erlauben") angehakt ist, entscheidet Core Data von Wert zu Wert, ob es die Bytes in die Zeile einbettet oder als separate Datei schreibt, sobald sie etwa 100 KB überschreiten. Diese externen Blobs werden in einem versteckten Support-Verzeichnis neben dem Speicher abgelegt — .<storename>_SUPPORT/_EXTERNAL_DATA/ — mit UUID-Dateinamen, und die Datenbankzeile behält nur eine Referenz auf diese Datei, nicht die Bytes.
Die .sqlite allein wiederherzustellen bringt also jedes skalare Attribut und jeden eingebetteten Blob zurück, aber für extern gespeicherte Daten liefert sie die Referenz, nicht das Bild. Wenn du zusätzlich den Ordner _EXTERNAL_DATA aus demselben App-Container hast, finden diese Dateien über den Namen wieder zusammen; ist dieses Verzeichnis weg, kann keine Datenbankwiederherstellung Bytes rekonstruieren, die nie in der Datenbank waren. Und da ein Core-Data-Speicher den gesamten privaten Zustand einer App enthalten kann — jede Notiz, jede Nachricht, jeden Gesundheitsdatensatz oder jedes Konto, das ein Nutzer je gespeichert hat — läuft all das in deinem Browser: Die Datei wird von der Festplatte gelesen, im Tab neu aufgebaut, und du kannst im Netzwerk-Tab bestätigen, dass kein einziges Byte dein Gerät verlässt.
Was sich reparieren lässt und was nicht
Kann repariert werden
- "database disk image is malformed" (SQLITE_CORRUPT / Code 11) durch eine beschädigte B-Baum-Seite oder einen falschen Zellenzeiger, wobei die dahinterliegenden ZENTITY-Seiten intakt sind
- Zeilen in jeder ZENTITY-Tabelle, die sich noch decodieren lassen — herausgezogen und in einen frischen, öffenbaren .sqlite-Speicher wieder eingefügt
- Nicht per Checkpoint gesicherte bestätigte Änderungen, überlagert aus einer bereitgestellten -wal-Begleitdatei (die frischesten Zeilen nach einem Absturz oder erzwungenen Beenden)
- Ein Speicher, dessen 16-Byte-Header oder Schema beschädigt ist, dessen Blattseiten aber überleben — das Schema wird aus den Seiten neu aufgebaut
- Zeilen, welche die B-Baum-Zeiger nicht mehr erreichen, wiederhergestellt durch einen rohen Seiten-Sweep in eine lost_and_found-Tabelle, mit neu aufgebautem Z_PRIMARYKEY.Z_MAX
Kann nicht repariert werden
- Zeilen, die physisch überschrieben oder am Dateiende abgeschnitten wurden — diese Bytes existieren nicht mehr auf der Festplatte
- Externe Binär-Blobs, die unter .<storename>_SUPPORT/_EXTERNAL_DATA gespeichert sind, wenn du diese Dateien nicht ebenfalls hast — die Zeile enthält nur eine Referenz
- Verschlüsselte Speicher (NSFileProtectionComplete-Daten, die kopiert wurden, während das Gerät gesperrt war, oder SQLCipher) ohne den Schlüssel — die Seiten werden als Chiffretext gelesen
- Die -shm-Datei für sich allein: Sie ist ein wieder aufbaubarer WAL-Index, nicht deine Daten — die -wal ist die Datei, welche die aktuellen Commits trägt
- Eine Garantie, dass die wiederhergestellten Daten vollständig oder korrekt sind — Daten aus einem beschädigten Speicher sind immer verdächtig; überprüfe sie gegen eine bekannte, verlässliche Quelle
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.