Das Symptom
Eine kleine Anwendung mit SQLite-Datenbank läuft seit Jahren auf einem Rechner. Dann
wird die Datei auf eine Netzfreigabe gelegt, damit ein zweiter Rechner dieselben Daten
sieht. Erst scheint alles zu gehen. Dann fehlen Datensätze, die auf dem einen Rechner
eingetragen wurden, auf dem anderen; ein Rechner meldet eine beschädigte Datenbank, der
andere arbeitet mit einem Stand von gestern weiter. Neben der Datei liegen zwei
Begleiter, datenbank.db-wal und datenbank.db-shm, und niemand weiß, wer sie
angelegt hat.
Der Verdacht fällt auf das Netz, auf die Freigabe, auf den zweiten Rechner. Es ist keiner von ihnen allein. Es ist die Kombination.
Die „Layer 8“-Wahrheit
Die Datenbank läuft im WAL-Modus, dem Write-Ahead-Logging, das jemand per PRAGMA journal_mode=WAL eingeschaltet hat, weil Leser und Schreiber sich dann nicht blockieren. Die Dokumentation von SQLite führt dazu eine Liste von Nachteilen, und der erste ist der hier entscheidende: „All processes using a database must be on the same host computer; WAL does not work over a network filesystem.“ Begründung gleich dahinter: WAL verlangt, dass alle Prozesse eine kleine Menge Speicher teilen, und Prozesse auf getrennten Rechnern können offensichtlich keinen Speicher miteinander teilen.
Dieser gemeinsame Speicher ist der wal-index, eine Struktur, mit der Leser schnell
finden, welche Seiten im WAL liegen, statt das ganze WAL zu durchsuchen. „The wal-index
greatly improves the performance of readers, but the use of shared memory means that
all readers must exist on the same machine.“ Umgesetzt ist er als gewöhnliche Datei
neben der Datenbank, die -shm-Datei, per mmap eingeblendet. Die Dokumentation
beschreibt, warum es ausgerechnet eine Datei im selben Verzeichnis sein muss: Nur so
sei zu garantieren, dass alle Prozesse, die auf dieselbe Datenbankdatei zugreifen,
denselben gemeinsamen Speicher verwenden. Diese Garantie endet am Rechner. Auf dem
Netzlaufwerk sehen beide Rechner dieselbe -shm-Datei, blenden sie aber in zwei
getrennte Speicher ein. Jeder hält seinen Index für den einzigen, und beide schreiben
in dasselbe WAL. Dass die Daten eine Weile zusammenpassen, ist Glück, kein Beweis.
Was getrennte Speicher anrichten, sagt dieselbe Seite an einem verwandten Fall, Prozessen
mit verschiedenem Wurzelverzeichnis: Sie sehen verschiedene Dateien und benutzen damit
„different shared memory areas, leading to database corruption“.
Es gibt eine Ausnahme, und sie bestätigt die Regel: „Beginning in SQLite version
3.7.4 (2010-12-07), WAL databases can be created, read, and written even if shared
memory is unavailable as long as the locking_mode is set to EXCLUSIVE before the first
attempted access.“ Dann wird laut Dokumentation nie ein wal-index angelegt, und der
eine Prozess darf arbeiten, weil er garantiert allein ist. Die Verbindung bleibt
exklusiv, solange der Journal-Modus WAL ist; PRAGMA locking_mode=NORMAL ist in
diesem Zustand wirkungslos. Das hilft einem Dienst, der seine Datei besitzt. Es hilft
nicht zwei Rechnern, die sich eine teilen wollen, denn genau das Teilen schließt es
aus.
Die ehrliche Antwort ist unbequem: SQLite ist für eine Datei auf einem Rechner gebaut. Wer dieselben Daten von mehreren Rechnern aus schreiben will, braucht einen Prozess, der die Datei allein besitzt und die anderen über das Netz bedient, oder einen Datenbankserver, der genau dafür gemacht ist. Die Freigabe war nie die Lösung. Sie war nur die Stelle, an der der Fehler sichtbar wurde.