Kodokon kodokon.com

Nebenläufigkeit: Sperren, Isolationsstufen, Phantom-Reads

Verstehe das Sperrmodell von SQLite, seine Transaktionen und die vom SQL-Standard beschriebenen Isolationsanomalien.

12 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

SQLite sperrt auf der Ebene der gesamten Datenbankdatei, nicht der Zeile. Im Standardmodus (Rollback-Journal) teilen sich Leser eine SHARED-Sperre, aber nur ein Schreiber zur Zeit kann die EXCLUSIVE-Sperre erhalten. Folge: Schreibvorgänge werden serialisiert. Erstellen wir eine Tabelle, um mit Transaktionen zu arbeiten.

SQL
CREATE TABLE accounts (
  id INTEGER PRIMARY KEY,
  balance INTEGER NOT NULL
);

INSERT INTO accounts (id, balance)
VALUES (1, 100), (2, 50);

-- Read: deferred transaction (default)
BEGIN;
SELECT balance FROM accounts WHERE id = 1;
COMMIT;
Eine explizite Transaktion, umschlossen von BEGIN/COMMIT.

Die Sperre entwickelt sich in Stufen: UNLOCKED, dann SHARED (Lesen), RESERVED (Schreibabsicht), PENDING und schließlich EXCLUSIVE (Schreiben). Hält eine Transaktion bereits die Schreibsperre, erhält eine andere den Fehler SQLITE_BUSY. Statt sofort fehlzuschlagen, lege mit busy_timeout eine Wartezeit fest und aktiviere den WAL-Modus, damit Leser und der Schreiber parallel vorankommen.

SQL
-- A single writer, concurrent readers
PRAGMA journal_mode = WAL;

-- Wait up to 5 s if the database is busy
PRAGMA busy_timeout = 5000;
WAL entsperrt nebenläufige Lesevorgänge; busy_timeout wartet.

Der SQL-Standard beschreibt drei Anomalien: den Dirty Read (nicht committete Daten lesen), den Non-Repeatable Read (eine Zeile erneut lesen und einen anderen Wert sehen) und den Phantom-Read (eine Bereichsabfrage erneut ausführen und neue Zeilen auftauchen sehen). Weil SQLite die Schreiber serialisiert, verhält es sich faktisch wie SERIALIZABLE: Diese Anomalien treten nicht auf (außerhalb des Shared-Cache-Modus mit read_uncommitted). Anderswo wählst du die Stufe explizit.

SQL
-- Atomic transfer: immediate write lock
BEGIN IMMEDIATE;
UPDATE accounts
SET balance = balance - 10
WHERE id = 1;
UPDATE accounts
SET balance = balance + 10
WHERE id = 2;
COMMIT;
BEGIN IMMEDIATE reserviert die Sperre vor jedem Schreibvorgang.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Welche Isolationsstufe bietet SQLite in der Praxis?
    • READ UNCOMMITTED, Dirty Reads sind standardmäßig möglich
    • READ COMMITTED, wie PostgreSQL
    • SERIALIZABLE, weil immer nur ein einziger Schreiber handelt
    • Keine, es gibt keine Transaktionen
  2. Was ist der Unterschied zwischen BEGIN und BEGIN IMMEDIATE?
    • BEGIN IMMEDIATE nimmt die Schreibsperre sofort; BEGIN wartet auf den ersten Schreibzugriff
    • BEGIN IMMEDIATE deaktiviert das Rollback-Journal
    • BEGIN IMMEDIATE erstellt eine neue Datenbank
    • Es gibt keinen Verhaltensunterschied
  3. Was ist ein Phantom-Read?
    • Einen Wert lesen, den eine andere Transaktion noch nicht committet hat
    • Dieselbe Zeile erneut lesen und einen anderen Wert erhalten
    • Eine Bereichsabfrage erneut ausführen und in der Zwischenzeit eingefügte neue Zeilen auftauchen sehen
    • Eine gerade gelöschte Zeile lesen