warum Write Skew das letzte offene Loch ist
Snapshot Isolation gilt zu Recht als komfortabler Kompromiss: Sie verhindert Dirty Reads, Non-repeatable Reads und die meisten Phantom Reads, ohne den Sperraufwand eines vollständigen Serializable-Levels. Trotzdem lässt sie eine subtile, aber gefährliche Anomalie zu, den sogenannten Write Skew, bei dem zwei Transaktionen unabhängig voneinander konsistent erscheinende Entscheidungen treffen, die zusammen eine Geschäftsregel verletzen. Serializable Snapshot Isolation, kurz SSI, schließt genau dieses letzte Loch. Dieser Artikel erklärt beide Isolationsstufen im Detail und arbeitet ein klassisches Write-Skew-Beispiel Schritt für Schritt durch.
Inhaltsverzeichnis
- 1. Das Grundprinzip von Snapshot Isolation
- 2. Welche Anomalien Snapshot Isolation zuverlässig vermeidet
- 3. Die Write-Skew-Anomalie: das klassische Beispiel mit dem Bereitschaftsdienst
- 4. Warum Write Skew unter reiner Snapshot Isolation überhaupt möglich ist
- 5. Serializable Snapshot Isolation: die Grundidee
- 6. Implementierungen: PostgreSQL SERIALIZABLE im Vergleich zu klassischem Locking
- 7. Das Bereitschaftsdienst-Beispiel unter SSI erneut durchgespielt
- 8. Die Kosten von SSI: Overhead und erhöhte Abbruchrate
- 9. Praxisempfehlung: wann Snapshot Isolation reicht und wann SSI nötig ist
- 10. Zusammenfassung
- 11. FAQ
1. Das Grundprinzip von Snapshot Isolation
Unter Snapshot Isolation sieht jede Transaktion beim ersten Lesezugriff einen konsistenten Schnappschuss der Datenbank, so wie sie zum Startzeitpunkt der Transaktion aussah, und zwar unabhängig davon, welche anderen Transaktionen währenddessen parallel schreiben. Änderungen anderer, gleichzeitig laufender Transaktionen bleiben für die eigene Transaktion unsichtbar, bis diese selbst committet und einen neuen Snapshot beginnt.
Beim Schreiben gilt die Regel First Committer Wins: Versuchen zwei Transaktionen, dieselbe Zeile zu ändern, gewinnt diejenige, die zuerst committet, während die zweite mit einem expliziten Konflikt abgewiesen wird und einen Retry benötigt. Dieser Mechanismus verhindert verlorene Updates zuverlässig, solange sich der Konflikt tatsächlich auf dieselbe physische Zeile bezieht.
-- PostgreSQL: explizit unter Snapshot Isolation arbeiten
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT kontostand FROM konto WHERE konto_id = 1;
-- Snapshot wird beim ersten Read fixiert, spätere parallele
-- Änderungen an anderen Zeilen bleiben für diese Transaktion unsichtbar
UPDATE konto SET kontostand = kontostand - 100 WHERE konto_id = 1;
COMMIT;
2. Welche Anomalien Snapshot Isolation zuverlässig vermeidet
Dirty Reads sind unter Snapshot Isolation grundsätzlich ausgeschlossen, weil eine Transaktion niemals Daten aus einer nicht committeten, parallelen Transaktion sieht. Non-repeatable Reads treten ebenfalls nicht auf, weil derselbe Snapshot für die gesamte Dauer der Transaktion stabil bleibt, ein zweites SELECT auf dieselbe Zeile liefert also garantiert denselben Wert wie das erste.
Auch Phantom Reads werden durch den fixierten Snapshot weitgehend verhindert: Eine Bereichsabfrage liefert innerhalb derselben Transaktion konsistent dieselbe Menge an Zeilen, selbst wenn parallel neue, zum Filter passende Zeilen eingefügt werden. Diese drei klassischen Anomalien aus dem SQL-Standard deckt Snapshot Isolation also vollständig ab, was sie in der Praxis für die meisten Anwendungsfälle bereits ausreichend robust macht.
3. Die Write-Skew-Anomalie: das klassische Beispiel mit dem Bereitschaftsdienst
Das Standardbeispiel für Write Skew stammt aus einem Krankenhaus-Bereitschaftsdienst: Es gibt eine Geschäftsregel, dass mindestens eine Ärztin oder ein Arzt im Dienst sein muss. Zwei diensthabende Personen, Ärztin A und Arzt B, entscheiden unabhängig voneinander, sich krankzumelden. Beide lesen zunächst, wie viele Personen aktuell im Dienst sind, beide sehen zwei, und beide schließen daraus korrekt, dass sich jeweils eine Person abmelden darf, ohne die Mindestbesetzung zu unterschreiten.
Beide Transaktionen lesen also dieselbe Ausgangssituation, treffen jeweils für sich genommen eine plausible, regelkonforme Entscheidung und schreiben anschließend jeweils eine andere Zeile, nämlich ihre eigene Abmeldung. Da beide Zeilen verschieden sind, greift die First-Committer-Wins-Regel nicht, beide Commits gelingen, und am Ende sind beide Personen abgemeldet, obwohl die Geschäftsregel mindestens eine im Dienst verlangt.
-- Transaktion A (Aerztin A meldet sich krank)
BEGIN;
SELECT count(*) FROM dienst WHERE aktiv = true; -- liefert 2
UPDATE dienst SET aktiv = false WHERE person = 'Aerztin A';
COMMIT;
-- Transaktion B (Arzt B meldet sich krank), zeitgleich gestartet
BEGIN;
SELECT count(*) FROM dienst WHERE aktiv = true; -- liefert ebenfalls 2
UPDATE dienst SET aktiv = false WHERE person = 'Arzt B';
COMMIT;
-- Ergebnis: 0 Personen im Dienst, Geschäftsregel verletzt
4. Warum Write Skew unter reiner Snapshot Isolation überhaupt möglich ist
Der First-Committer-Wins-Mechanismus von Snapshot Isolation erkennt ausschließlich direkte Schreibkonflikte auf derselben physischen Zeile. Im Bereitschaftsdienst-Beispiel schreibt jede Transaktion jedoch eine andere Zeile, die Lesekonsistenz-Verletzung entsteht nicht durch einen Konflikt beim Schreiben, sondern durch die logische Abhängigkeit zwischen dem, was gelesen wurde, und dem, was später von einer anderen Transaktion geschrieben wird.
Diese Art von Abhängigkeit nennt sich in der Literatur eine rw-Antidependency: Transaktion A liest eine Menge an Zeilen, die von Transaktion B anschließend verändert wird, und umgekehrt liest Transaktion B eine Menge, die von Transaktion A verändert wird. Erst wenn beide Richtungen dieser Abhängigkeit gleichzeitig auftreten, entsteht die klassische Write-Skew-Anomalie, und genau dieses Muster kann ein reiner Zeilen-Konflikt-Check per Definition nicht erkennen.
5. Serializable Snapshot Isolation: die Grundidee
Serializable Snapshot Isolation baut auf demselben Snapshot-Mechanismus auf, ergänzt ihn aber um eine zusätzliche Laufzeitüberwachung, die genau nach den oben beschriebenen rw-Antidependencies zwischen gleichzeitig laufenden Transaktionen sucht. Erkennt die Datenbank ein Muster, das potenziell zu einer nicht-serialisierbaren Ausführung führen würde, bricht sie eine der beteiligten Transaktionen gezielt mit einem Serialization-Failure-Fehler ab, statt beide committen zu lassen.
Der entscheidende konzeptionelle Unterschied zu klassischem, sperrenbasiertem Serializable ist, dass SSI optimistisch bleibt: Es werden weiterhin keine Sperren für Lesezugriffe gehalten, sondern lediglich Abhängigkeiten mitprotokolliert und im Konfliktfall nachträglich aufgelöst, was unter geringer bis mittlerer Konfliktrate deutlich weniger Overhead erzeugt als durchgängiges pessimistisches Sperren.
6. Implementierungen: PostgreSQL SERIALIZABLE im Vergleich zu klassischem Locking
PostgreSQL implementiert Serializable Snapshot Isolation seit Version 9.1 direkt hinter dem Isolationslevel SERIALIZABLE, wählbar über eine einfache Transaktionseinstellung, ohne dass sich am eigentlichen SQL etwas ändert. Andere Systeme, etwa klassische, ältere Implementierungen von SERIALIZABLE in verschiedenen Datenbanken, setzen stattdessen auf strikte Zwei-Phasen-Sperrverfahren, bei denen Lesezugriffe echte Sperren belegen, die andere Transaktionen aktiv blockieren, statt lediglich Konflikte im Nachhinein zu erkennen.
Der praktische Unterschied zeigt sich vor allem unter Last: Sperrenbasiertes Serializable kann bei vielen gleichzeitigen Lesezugriffen auf überlappende Datenbereiche zu spürbarer Blockierung und im schlimmsten Fall zu Deadlocks führen, während SSI Lesezugriffe grundsätzlich nie blockiert, dafür aber im Konfliktfall eine bereits laufende Transaktion mit einem Abbruch zurückweist, den die Anwendung durch einen erneuten Versuch auffangen muss.
7. Das Bereitschaftsdienst-Beispiel unter SSI erneut durchgespielt
Unter SERIALIZABLE in PostgreSQL laufen beide Transaktionen aus dem Beispiel oben zunächst identisch ab: Beide lesen den Zählwert 2, beide entscheiden sich für die Abmeldung. Beim Versuch, als zweite Transaktion zu committen, erkennt PostgreSQL jedoch die rw-Antidependency zwischen beiden Transaktionen und lehnt diesen Commit mit einer expliziten Serialization-Failure-Fehlermeldung ab, während die erste Transaktion normal committet.
Die Anwendung muss auf diesen Fehler vorbereitet sein und die betroffene Transaktion erneut ausführen. Beim zweiten Versuch liest die wiederholte Transaktion dann den bereits reduzierten Zählwert von 1 und lehnt die Abmeldung korrekt selbst ab, wodurch die Geschäftsregel der Mindestbesetzung tatsächlich eingehalten bleibt, ganz ohne dass eine explizite Sperre auf die Zähltabelle nötig gewesen wäre.
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT count(*) FROM dienst WHERE aktiv = true;
UPDATE dienst SET aktiv = false WHERE person = 'Aerztin A';
COMMIT;
-- Die zweite, parallele Transaktion erhält beim COMMIT:
-- ERROR: could not serialize access due to read/write dependencies
-- Anwendung muss die Transaktion mit Retry-Logik erneut ausführen
8. Die Kosten von SSI: Overhead und erhöhte Abbruchrate
SSI ist nicht kostenlos: Die Datenbank muss für jede Transaktion mitverfolgen, welche Zeilenmengen gelesen und geschrieben wurden, um mögliche rw-Antidependencies überhaupt erkennen zu können, was zusätzlichen Speicher- und Rechenaufwand gegenüber reiner Snapshot Isolation bedeutet. Bei Workloads mit sehr vielen, stark konkurrierenden Transaktionen auf überlappenden Datenbereichen steigt außerdem die Abbruchrate durch Serialization Failures spürbar.
Jede Anwendung, die SERIALIZABLE einsetzt, muss deshalb zwingend eine Retry-Logik für genau diesen Fehlerfall implementieren, üblicherweise mit einer begrenzten Anzahl an Wiederholungsversuchen und einem kurzen, zufälligen Backoff dazwischen, um wiederholte Kollisionen zwischen denselben Transaktionen unwahrscheinlicher zu machen.
9. Praxisempfehlung: wann Snapshot Isolation reicht und wann SSI nötig ist
Reine Snapshot Isolation genügt für die überwiegende Mehrheit der Anwendungsfälle, insbesondere wenn Geschäftsregeln sich stets auf eine einzelne Zeile beziehen lassen, etwa Kontostände oder Lagerbestände, bei denen der First-Committer-Wins-Mechanismus Konflikte zuverlässig erkennt. Für solche Fälle bietet SSI keinen zusätzlichen Nutzen, verursacht aber trotzdem den zusätzlichen Overhead der Abhängigkeitsverfolgung.
SSI wird dann relevant, wenn eine Geschäftsregel sich über mehrere, voneinander unabhängige Zeilen erstreckt und mehrere gleichzeitige Transaktionen jeweils für sich genommen konsistente, aber gemeinsam widersprüchliche Entscheidungen treffen können, wie im Bereitschaftsdienst-Beispiel. Kapazitätsgrenzen, gegenseitig ausschließende Zustände über mehrere Entitäten hinweg und ähnliche Constraint-artige Regeln sind typische Kandidaten, bei denen sich der Umstieg auf SERIALIZABLE trotz des Mehraufwands lohnt.
| Anomalie | Read Committed | Snapshot Isolation | Serializable Snapshot Isolation |
|---|---|---|---|
| Dirty Read | Verhindert | Verhindert | Verhindert |
| Non-repeatable Read | Möglich | Verhindert | Verhindert |
| Phantom Read | Möglich | Weitgehend verhindert | Verhindert |
| Write Skew | Möglich | Möglich | Verhindert |
| Overhead gegenüber Read Committed | Keiner | Moderat | Höher, plus Abbruchrisiko |
Mironsoft
Datenbank-Optimierung, Query-Tuning und Migrationen
SQL-Abfragen, die bei Wachstum immer langsamer werden?
Wir analysieren und optimieren SQL-Datenbanken unabhängig vom eingesetzten System, planen sichere Migrationen und Schema-Änderungen und bringen Teams Query-Optimierung praxisnah bei.
Query-Optimierung
Langsame Abfragen analysieren und mit Indizes und Explain-Plänen gezielt beschleunigen.
Migrations-Planung
Schema-Änderungen und Datenmigrationen sicher und ohne Downtime umsetzen.
Team-Schulung
SQL-Grundlagen und Performance-Denken praxisnah im Entwicklerteam verankern.
10. Zusammenfassung
Snapshot Isolation vs. SSI: das Wichtigste auf einen Blick
Snapshot Isolation
Vermeidet Dirty Reads, Non-repeatable Reads und die meisten Phantom Reads zuverlässig.
Write Skew
Zwei Transaktionen treffen unabhängig konsistente Entscheidungen, die gemeinsam eine Regel verletzen.
SSI
Erkennt rw-Antidependencies zur Laufzeit und bricht eine der Transaktionen gezielt ab.
PostgreSQL
Implementiert SSI seit Version 9.1 direkt hinter dem Isolationslevel SERIALIZABLE.