drei Anomalien, drei Zwei-Transaktionen-Timelines
Dirty Read, Non-Repeatable Read und Phantom Read sind die drei Nebenlaeufigkeitsanomalien, die der SQL-Standard bei der Definition von Isolation Levels als Referenz nutzt. Jede Anomalie entsteht, wenn zwei Transaktionen zeitlich ueberlappen, und jede laesst sich mit einer konkreten Zwei-Transaktionen-Timeline reproduzieren, statt sie nur abstrakt zu beschreiben.
Inhaltsverzeichnis
- 1. Warum Nebenlaeufigkeitsanomalien entstehen
- 2. Dirty Read: die riskanteste Anomalie
- 3. Non-Repeatable Read: derselbe Wert, zweimal unterschiedlich
- 4. Phantom Read: neue Zeilen mitten in der Transaktion
- 5. Lost Update: die vierte, oft uebersehene Anomalie
- 6. Welches Isolation Level welche Anomalie verhindert
- 7. Anomalien in der eigenen Anwendung reproduzieren und testen
- 8. Praktische Auswirkungen aus dem echten Leben
- 9. Strategien zur Vermeidung dieser Anomalien
- 10. Zusammenfassung
- 11. FAQ
1. Warum Nebenlaeufigkeitsanomalien entstehen
Nebenlaeufigkeitsanomalien wie Dirty Read, Non-Repeatable Read und Phantom Read entstehen immer dann, wenn zwei oder mehr Transaktionen sich zeitlich ueberlappen und mindestens eine davon Daten schreibt, die die andere liest. Ohne jede Isolation waere jede dieser Anomalien staendig moeglich, mit vollstaendiger Isolation waere keine davon moeglich, aber der Durchsatz waere stark eingeschraenkt. Der SQL-Standard nutzt genau diese drei Anomalien, um die vier Isolation Levels formal zu definieren.
Der entscheidende Unterschied zu abstrakten Definitionen ist, dass jede dieser Anomalien sich an einer konkreten Timeline aus zwei Transaktionen, hier T1 und T2 genannt, exakt nachvollziehen laesst. Wer einmal gesehen hat, wie ein Dirty Read oder ein Phantom Read Schritt fuer Schritt entsteht, erkennt das Risikoprofil des eigenen Isolation Levels deutlich schneller als bei einer rein theoretischen Beschreibung.
Die folgenden Abschnitte zeigen jede Anomalie mit einer eigenen Timeline, ergaenzt um die seltener genannte Lost-Update-Anomalie, und fassen am Ende zusammen, welches Isolation Level welche Kombination dieser Probleme ausschliesst.
2. Dirty Read: die riskanteste Anomalie
Ein Dirty Read tritt auf, wenn eine Transaktion Daten liest, die eine andere Transaktion bereits geschrieben, aber noch nicht committet hat. Wird die schreibende Transaktion anschliessend zurueckgerollt, hat die lesende Transaktion mit Werten gearbeitet, die niemals tatsaechlich in der Datenbank existiert haben. Diese Anomalie ist deshalb die gefaehrlichste der drei, weil sie auf Basis von Daten entscheidet, die formal nie gueltig waren.
-- Dirty Read Timeline (nur unter READ UNCOMMITTED moeglich)
-- T1 (Session A):
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE account_id = 1;
-- balance ist jetzt 500, aber noch NICHT committet
-- T2 (Session B), parallel, liest den ungueltigen Zwischenstand:
BEGIN;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT balance FROM accounts WHERE account_id = 1; -- liest 500 (dirty!)
COMMIT;
-- T2 hat mit einem Wert gearbeitet, der nie final existierte
-- T1 rollt zurueck:
ROLLBACK;
-- balance ist wieder der urspruengliche Wert
-- T2 basiert weiterhin auf dem falschen, nie committeten Zwischenwert
3. Non-Repeatable Read: derselbe Wert, zweimal unterschiedlich
Ein Non-Repeatable Read tritt auf, wenn eine Transaktion dieselbe Zeile zweimal liest und zwischen den beiden Lesevorgaengen eine andere Transaktion diese Zeile aendert und committet. Die lesende Transaktion sieht dann zwei unterschiedliche, jeweils fuer sich korrekte Werte innerhalb ihrer eigenen, noch laufenden Transaktion, was zu inkonsistenten Berechnungen fuehren kann, wenn beide Werte in derselben Geschaeftslogik verwendet werden.
-- Non-Repeatable Read Timeline (moeglich unter READ COMMITTED)
-- T1 (Session A):
BEGIN;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT price FROM products WHERE product_id = 42; -- liefert 19.99
-- T2 (Session B), committet zwischendurch:
BEGIN;
UPDATE products SET price = 24.99 WHERE product_id = 42;
COMMIT;
-- T1, gleiche Transaktion, zweite Abfrage derselben Zeile:
SELECT price FROM products WHERE product_id = 42; -- liefert jetzt 24.99
COMMIT;
-- Beide Preise waren zu ihrem jeweiligen Zeitpunkt korrekt,
-- aber innerhalb einer einzigen Transaktion widerspruechlich
4. Phantom Read: neue Zeilen mitten in der Transaktion
Ein Phantom Read tritt auf, wenn eine Transaktion eine Bereichsabfrage zweimal ausfuehrt und zwischen den beiden Ausfuehrungen eine andere Transaktion neue Zeilen einfuegt, die in die Filterbedingung passen und beim zweiten Mal ploetzlich mit auftauchen. Anders als beim Non-Repeatable Read geht es hier nicht um geaenderte Werte einer bestehenden Zeile, sondern um zusaetzliche, vorher nicht vorhandene Zeilen.
-- Phantom Read Timeline (moeglich unter REPEATABLE READ
-- im strengen SQL-Standard, in PostgreSQL/InnoDB in der Praxis
-- durch MVCC bzw. Next-Key-Locks weitgehend verhindert)
-- T1 (Session A):
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT COUNT(*) FROM orders WHERE status = 'pending'; -- liefert 12
-- T2 (Session B), fuegt eine neue passende Zeile ein und committet:
BEGIN;
INSERT INTO orders (order_id, status, customer_id)
VALUES (9981, 'pending', 55);
COMMIT;
-- T1, gleiche Transaktion, wiederholte Bereichsabfrage:
SELECT COUNT(*) FROM orders WHERE status = 'pending';
-- unter strikter SQL-Standard-Interpretation koennte hier 13 stehen
-- PostgreSQL/InnoDB liefern durch Snapshot-Konsistenz weiterhin 12
COMMIT;
5. Lost Update: die vierte, oft uebersehene Anomalie
Neben den drei Standard-Anomalien des SQL-Standards gibt es die Lost-Update-Anomalie, die in der Praxis besonders haeufig auftritt, aber seltener explizit benannt wird. Zwei Transaktionen lesen denselben Wert, berechnen unabhaengig voneinander einen neuen Wert basierend auf dem gelesenen Ausgangswert, und schreiben ihn zurueck. Die zweite Schreiboperation ueberschreibt die erste vollstaendig, ohne dass die erste Aenderung jemals wirksam wurde, obwohl beide Transaktionen erfolgreich committeten.
-- Lost Update Timeline (moeglich unter READ COMMITTED,
-- durch REPEATABLE READ mit Snapshot-Isolation meist verhindert,
-- aber je nach Datenbank pruefpflichtig)
-- T1 (Session A):
BEGIN;
SELECT stock FROM inventory WHERE product_id = 42; -- liest 10
-- T2 (Session B), liest denselben Ausgangswert, parallel:
BEGIN;
SELECT stock FROM inventory WHERE product_id = 42; -- liest ebenfalls 10
UPDATE inventory SET stock = 10 - 3 WHERE product_id = 42; -- schreibt 7
COMMIT;
-- T1 berechnet unabhaengig auf Basis des urspruenglich gelesenen Werts 10:
UPDATE inventory SET stock = 10 - 5 WHERE product_id = 42; -- schreibt 5
COMMIT;
-- Endstand: 5, obwohl 3 plus 5 gleich 8 Einheiten haetten abgezogen
-- werden muessen, T2s Aenderung ist komplett verloren gegangen
6. Welches Isolation Level welche Anomalie verhindert
Die vier Isolation Levels des SQL-Standards schliessen diese Anomalien schrittweise aus, wobei die praktische Implementierung je Datenbank teilweise strenger ist, als der Standard verlangt. Die folgende Tabelle fasst zusammen, welche Anomalie bei welchem Isolation Level laut Standard noch moeglich ist.
| Isolation Level | Dirty Read | Non-Repeatable Read | Phantom Read |
|---|---|---|---|
| READ UNCOMMITTED | Moeglich | Moeglich | Moeglich |
| READ COMMITTED | Verhindert | Moeglich | Moeglich |
| REPEATABLE READ | Verhindert | Verhindert | Laut Standard moeglich |
| SERIALIZABLE | Verhindert | Verhindert | Verhindert |
Diese Tabelle beschreibt die Mindestanforderung des SQL-Standards, nicht zwingend das tatsaechliche Verhalten jeder Datenbank. MySQL/InnoDB verhindert Phantom Reads bereits unter REPEATABLE READ durch Next-Key-Locks weitgehend, PostgreSQL verhindert sie durch seine Snapshot-Isolation ebenfalls, erkennt aber bei echten Schreibkonflikten stattdessen eine Serialization Failure. Fuer eine konkrete Anwendung zaehlt immer das dokumentierte Verhalten der eingesetzten Datenbank, nicht nur die Tabelle des Standards.
7. Anomalien in der eigenen Anwendung reproduzieren und testen
Nebenlaeufigkeitsanomalien wie Dirty Read oder Phantom Read lassen sich gezielt reproduzieren, indem man zwei parallele Datenbanksitzungen manuell oder in einem Testskript oeffnet und die Anweisungen in der richtigen Reihenfolge verschraenkt ausfuehrt. Das ist der zuverlaessigste Weg, das tatsaechliche Verhalten der eigenen Datenbank unter dem konfigurierten Isolation Level zu pruefen, statt sich allein auf die Dokumentation zu verlassen.
-- Manueller Reproduktionstest mit zwei psql-Sitzungen (PostgreSQL)
-- Terminal 1:
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT * FROM inventory WHERE product_id = 42;
-- Terminal 2, waehrend Terminal 1 offen bleibt:
BEGIN;
UPDATE inventory SET stock = stock - 1 WHERE product_id = 42;
COMMIT;
-- Zurueck in Terminal 1, gleiche Abfrage wiederholen:
SELECT * FROM inventory WHERE product_id = 42;
-- Unterschied zum ersten SELECT zeigt Non-Repeatable Read
COMMIT;
-- Fuer automatisierte Tests: zwei DB-Verbindungen im selben Testskript
-- oeffnen, Anweisungen ueber explizite Synchronisationspunkte
-- (Barriers, Locks) in die gewuenschte Reihenfolge zwingen
8. Praktische Auswirkungen aus dem echten Leben
Ein Non-Repeatable Read in einem Bankreport kann dazu fuehren, dass ein Kontoauszug innerhalb derselben Generierung zwei unterschiedliche Salden fuer denselben Zeitpunkt ausweist, wenn ein Buchungsvorgang genau waehrend der Report-Erstellung committet. Ein Phantom Read in einem Lagerverwaltungssystem kann dazu fuehren, dass eine Verfuegbarkeitspruefung eine bestimmte Anzahl freier Plaetze zaehlt, waehrend parallel neue Reservierungen eingefuegt werden, wodurch am Ende mehr Plaetze vergeben werden als tatsaechlich vorhanden sind.
Die Lost-Update-Anomalie ist in der Praxis besonders tueckisch, weil sie keine Fehlermeldung ausloest, beide beteiligten Transaktionen committen erfolgreich. Ein typisches Beispiel ist ein Warenkorb-System, bei dem zwei parallele Requests denselben Lagerbestand lesen, beide unabhaengig eine Reduzierung berechnen und die zweite Schreiboperation die erste stillschweigend ueberschreibt. Der Lagerbestand erscheint korrekt, ist aber tatsaechlich zu hoch, was erst bei einer spaeteren Inventur auffaellt.
9. Strategien zur Vermeidung dieser Anomalien
Die naheliegendste Strategie gegen alle drei Standard-Anomalien ist die Wahl eines ausreichend starken Isolation Levels, wie im vertiefenden Beitrag zu Isolation Levels beschrieben. REPEATABLE READ oder SERIALIZABLE schliessen die meisten dieser Probleme systematisch aus, kosten dafuer aber Durchsatz und erfordern bei SERIALIZABLE zusaetzlich Retry-Logik fuer Serialization Failures im Anwendungscode.
Eine ergaenzende, oft praktischere Strategie ist gezieltes Locking auf Zeilenebene: Pessimistic Locking mit SELECT FOR UPDATE verhindert Lost Updates und Non-Repeatable Reads fuer genau die betroffenen Zeilen, ohne das Isolation Level der gesamten Transaktion zu erhoehen. Optimistic Locking mit einer Versionsspalte erkennt Lost Updates nachtraeglich beim Schreiben und laesst die Anwendung gezielt reagieren, statt vorab pauschal zu sperren. Beide Ansaetze werden in den vertiefenden Beitraegen zu Pessimistic und Optimistic Locking im Detail behandelt.
10. Zusammenfassung
Dirty Read, Non-Repeatable Read und Phantom Read sind die drei Nebenlaeufigkeitsanomalien, die der SQL-Standard zur Definition der Isolation Levels nutzt, ergaenzt um die praktisch relevante Lost-Update-Anomalie. Jede entsteht aus einer bestimmten Ueberlappung zweier Transaktionen: ungueltige Zwischenwerte beim Dirty Read, widerspruechliche Werte derselben Zeile beim Non-Repeatable Read, zusaetzliche Zeilen beim Phantom Read und verloren gegangene Schreiboperationen beim Lost Update.
Wer diese Anomalien mit konkreten Zwei-Transaktionen-Timelines verstanden hat, kann das eigene Isolation Level gezielt anhand der tatsaechlich tolerierbaren Risiken waehlen, statt pauschal die staerkste oder schwaechste Stufe zu nutzen. Reproduktionstests mit zwei parallelen Sitzungen sind der zuverlaessigste Weg, das dokumentierte Verhalten einer Datenbank gegen das tatsaechliche Verhalten unter Last zu pruefen.
Dirty Read, Phantom Read, Non-Repeatable Read im Vergleich, das Wichtigste auf einen Blick
Dirty Read
Lesen nicht committeter Daten einer anderen Transaktion, nur unter READ UNCOMMITTED moeglich.
Non-Repeatable Read
Derselbe Wert liefert zweimal unterschiedliche Ergebnisse, moeglich bis einschliesslich READ COMMITTED.
Phantom Read
Neue Zeilen tauchen bei wiederholter Bereichsabfrage auf, laut Standard bis REPEATABLE READ moeglich.
Lost Update
Eine von zwei parallelen Schreiboperationen geht stillschweigend verloren, oft durch Locking vermeidbar.