MWCodebymw.de ↗
SQL

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 PRAGMA bei 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

#SQLite#WAL#PRAGMA#Performance#Betrieb

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 →