SQLite im Web – WAL-Modus und die PRAGMAs, die „database is locked" beenden
SQLite ist für kleine und mittlere Websites hervorragend – wenn man es richtig einstellt. Vier Zeilen beim Verbindungsaufbau entscheiden darüber, ob parallele Zugriffe funktionieren oder in einer Sperrmeldung enden.
Die häufigste Beschwerde über SQLite lautet „geht nur für eine Person gleichzeitig". Das stimmt in der Standardeinstellung fast, und es stimmt nicht mehr, sobald man den WAL-Modus einschaltet.
Die vier Zeilen
PRAGMA journal_mode = WAL; -- Leser blockieren den Schreiber nicht mehr
PRAGMA synchronous = NORMAL; -- guter Kompromiss aus Sicherheit und Tempo (mit WAL)
PRAGMA busy_timeout = 5000; -- 5 s warten statt sofort "database is locked"
PRAGMA foreign_keys = ON; -- Fremdschlüssel prüfen (steht standardmäßig AUS!)In PHP gehören sie direkt hinter den Verbindungsaufbau:
$db = new PDO('sqlite:' . $pfad);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec('PRAGMA journal_mode = WAL');
$db->exec('PRAGMA synchronous = NORMAL');
$db->exec('PRAGMA busy_timeout = 5000');
$db->exec('PRAGMA foreign_keys = ON');Was WAL tatsächlich ändert
Im klassischen Rollback-Journal sperrt ein Schreibvorgang die ganze Datei – alle Leser warten. Im WAL-Modus schreibt SQLite Änderungen in eine Begleitdatei (…-wal) und lässt Leser weiter auf dem alten Stand lesen. Ergebnis: viele Leser und ein Schreiber gleichzeitig. Für eine Website mit Formularen, Zählern und einem Admin ist das genau das Lastprofil.
Drei Dinge, die dazugehören:
- Es entstehen zwei Begleitdateien (
.wal,.shm). Sie gehören zur Datenbank – beim Kopieren oder Sichern nicht vergessen. - Der Modus ist dauerhaft in der Datei gespeichert; einmal setzen genügt, das
PRAGMAbei jedem Start schadet aber nicht. - Auf Netzlaufwerken (NFS) funktioniert WAL nicht zuverlässig, weil es gemeinsamen Speicher braucht.
busy_timeout ist wichtiger, als es aussieht
Ohne Zeitlimit gibt SQLite bei einer belegten Datei sofort auf – das ist die berüchtigte Meldung „database is locked". Mit busy_timeout wartet es die angegebenen Millisekunden und versucht es erneut. In der Praxis verschwindet der Fehler damit aus dem Protokoll.
Sichern im laufenden Betrieb
# Konsistente Kopie, auch während geschrieben wird:
sqlite3 daten.sqlite ".backup '/sicherung/daten-$(date +%F).sqlite'"Ein simples cp der Datei kann mitten in einer Transaktion landen. .backup (bzw. die Backup-API) erledigt das richtig.
Ab und zu aufräumen
PRAGMA wal_checkpoint(TRUNCATE); -- WAL-Datei in die Datenbank einarbeiten und kürzen
PRAGMA optimize; -- Statistiken auffrischen, günstig vor dem Verbindungsende
VACUUM; -- Datei neu packen (sperrt, also selten und geplant)Fallstrick
PRAGMA foreign_keys = ON gilt je Verbindung, nicht je Datenbank – wer es in einem Skript vergisst, schreibt dort fröhlich verwaiste Zeilen. Und synchronous = NORMAL ist mit WAL sicher gegen Programmabstürze, nicht aber gegen einen Stromausfall im falschen Moment; wenn jede einzelne Transaktion überleben muss, bleibt FULL die richtige Wahl. Zuletzt: WAL macht Schreibvorgänge nicht parallel – es bleibt bei einem Schreiber. Wer wirklich viele gleichzeitige Schreiber hat, braucht eine andere Datenbank.
Ich betreibe mehrere Projekte produktiv auf SQLite – wenn du wissen willst, ob das für deins reicht: bymw.de.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
Laufende Summe in SQL – SUM() OVER statt Schleife in der Anwendung
Kontostand nach jeder Buchung, kumulierter Umsatz im Jahr, Restbestand nach jeder Entnahme: Alles dieselbe Frage. Mit einer Fensterfunktion beantwortet die Datenbank sie in einer Abfrage – inklusive gleitendem Durchschnitt.
Generierte Spalten in SQLite – abgeleitete Werte, die nie veralten
Bruttopreis, Suchspalte in Kleinschreibung, das Jahr aus einem Zeitstempel: Solche Werte doppelt zu pflegen ist eine Einladung an die Inkonsistenz. SQLite kann sie selbst berechnen – und sogar indizieren.
Trigger in SQLite – updated_at, das niemand vergessen kann
Ein Zeitstempel, den die Anwendung setzen muss, ist irgendwann falsch – spätestens beim Import, beim Admin-Skript oder beim schnellen UPDATE von Hand. Ein Trigger nimmt der Anwendung die Pflicht ab.