Sperren und Deadlocks

Veröffentlicht am:

Die wichtigsten Befehle zum Merken

  • SELECT … FOR UPDATE — eine Zeilensperre für die Transaktion halten.
  • NOWAIT — eine nicht verfügbare Zeilensperre sofort melden.
  • COMMIT / ROLLBACK — Transaktion beenden und Sperren freigeben.

Befehle und Optionen

Befehl oder Syntax Bedeutung
psql -X -d lab Testdatenbank ohne psql-Startanpassungen öffnen.
CREATE TABLE / INSERT Eigene Testtabelle und Einzelzeile erzeugen.
PRIMARY KEY Eindeutige, nichtleere Zeilenkennung verlangen.
BEGIN Folgende Operationen in einer Transaktion halten.
SELECT * … WHERE id = 1 FOR UPDATE Zeile lesen und Update-Sperre erwerben.
NOWAIT Bei nicht verfügbarer Zeilensperre sofort scheitern.
COMMIT / ROLLBACK Transaktion bestätigen / verwerfen.
DROP TABLE / \q Testtabelle entfernen / psql beenden.

-- A1 nennt Sitzung und Reihenfolge. Semikolons beenden Anweisungen. Nach dem erwarteten Sperrfehler bleibt Bs Transaktion bis ROLLBACK abgebrochen.

Die entscheidenden Konzepte

1. Sperren koordinieren unverträgliche Operationen

Eine Sperre verhindert das gleichzeitige Ausführen unverträglicher Operationen. Datenbanken verwenden verschiedene Modi und Ebenen, etwa Tabellen- und Zeilensperren.

Eine PostgreSQL-Zeilensperre stoppt nicht alle Leser. Normale Snapshot-Abfragen können häufig weiterlaufen. Entscheidend ist, welche angeforderte Operation mit welcher gehaltenen Sperre kollidiert, nicht allein die Existenz einer Sperre.

2. Die Transaktion bestimmt die Haltedauer

Viele Datenbanksperren bleiben bis zum Transaktionsende bestehen. Ein schnelles Update mit langer Pause vor Commit kann andere Arbeit wesentlich länger blockieren als das Update selbst dauerte.

Halte Transaktionen kurz und warte beim Sperrbesitz nicht auf Menschen oder langsame externe Dienste. Auch eine untätige Verbindung innerhalb einer Transaktion kann ohne nennenswerten CPU-Verbrauch blockieren.

3. Ein Deadlock ist ein Zyklus, nicht bloß langes Warten

Hält A Zeile 1 und benötigt Zeile 2, während B Zeile 2 hält und Zeile 1 benötigt, kommt keine weiter. Diese zyklische Abhängigkeit ist ein Deadlock. Warten auf eine noch fortschrittsfähige Transaktion ist gewöhnliches Blockieren.

PostgreSQL erkennt Deadlocks und bricht einen Beteiligten ab. Die Anwendung muss dessen Fehler behandeln. Ein höherer Timeout löst keinen Abhängigkeitszyklus.

4. Feste Reihenfolge reduziert Konflikte, Wiederholungen behandeln Reste

Wer mehrere Ressourcen braucht, reduziert Deadlock-Möglichkeiten durch einheitliche Sperrreihenfolge. Kurze Transaktionen verkleinern zusätzlich die zeitliche Überlappung möglicher Konflikte.

Sofortfehler und Timeouts begrenzen Warten, übertragen die Entscheidung aber an den Aufrufer: gesamten Ablauf wiederholen, Konflikt zurückgeben oder abbrechen. Endlose Wiederholungen unter hoher Konkurrenz können die Reaktionsfähigkeit weiter verschlechtern.

Ein kleines Beispiel

Optional: Öffne den ersten Befehl in A und B mit gleicher Datenbank und gleichem Schema. Führe A1, B1, A2 und B2 aus. Gib nach Bs erwartetem Fehler ROLLBACK ein. Stoppe bei vorhandener Tabelle, statt diese zu verwenden.

psql -X -d lab
-- A1
CREATE TABLE btc_lock_lab (id integer PRIMARY KEY);
INSERT INTO btc_lock_lab VALUES (1);
BEGIN;
SELECT * FROM btc_lock_lab WHERE id = 1 FOR UPDATE;
-- B1
BEGIN;
SELECT * FROM btc_lock_lab WHERE id = 1 FOR UPDATE NOWAIT;
ROLLBACK;
-- A2
COMMIT;
-- B2
BEGIN;
SELECT * FROM btc_lock_lab WHERE id = 1 FOR UPDATE NOWAIT;
COMMIT;
DROP TABLE btc_lock_lab;
\q

B1 sollte bei offener A-Transaktion keine Sperre erhalten. Nach A2 sollte B2 die Zeile sperren und 1 zurückgeben. Kein Zeilenwert ändert sich: FOR UPDATE genügt zur Demonstration.

Das ist ein Sperrkonflikt, kein erzeugter Deadlock. DROP entfernt die Testtabelle nach Transaktionsende; beende anschließend beide Sitzungen. Bei Unterbrechung zuerst offene Transaktionen schließen oder zurückrollen, dann nur die selbst erzeugte Tabelle entfernen.

Merke dir: Eine Sperrwartezeit hat einen Blockierer; ein Deadlock hat einen Zyklus. Transaktionsgrenzen bestimmen die Dauer des Konflikts.