Datenbankadministration für Sysadmins

Transaktionen und Isolationsstufen: Garantien, Sperren und ihr Preis

Zwei Buchungen auf dasselbe Konto, beide fehlerfrei, beide bestätigt — und am Ende fehlt ein Betrag. Keine Datenbank hat dabei etwas falsch gemacht. Dieses Modul benennt die fünf Nebenläufigkeitsfehler, zeigt, welche Isolationsstufe welchen davon ausschließt, und beendet den häufigsten Irrtum im Datenbankbetrieb: dass eine niedrigere Stufe blockierende Schreibvorgänge löst.

Lehrtext · 10 Abschnitte · zuletzt geprüft: 2026-09-12

Fünf Fehler, die einen Namen haben

Sobald zwei Transaktionen gleichzeitig auf dieselben Daten zugreifen, können Ergebnisse entstehen, die bei einzelner Ausführung unmöglich wären. Diese Effekte heißen Anomalien, und sie haben feste Namen — wer sie benennen kann, kann die richtige Stufe wählen, statt sie zu raten.

Dirty ReadEine Transaktion liest eine Änderung, die eine andere noch nicht bestätigt hat. Wird die andere zurückgerollt, hat man einen Wert gelesen, den es nie gab.

Non-Repeatable ReadDieselbe Zeile wird innerhalb einer Transaktion zweimal gelesen und liefert zwei verschiedene Werte, weil dazwischen jemand anderes bestätigt hat.

Phantom ReadDieselbe Abfrage wird zweimal ausgeführt und liefert beim zweiten Mal zusätzliche Zeilen, weil jemand anderes passende Zeilen eingefügt hat.

SerialisierungsanomalieMehrere Transaktionen werden erfolgreich bestätigt, und das Gesamtergebnis passt zu keiner Reihenfolge, in der man sie nacheinander hätte ausführen können.

Lost UpdateZwei Transaktionen lesen denselben Wert, rechnen beide damit und schreiben beide zurück. Die zweite überschreibt die erste, ohne sie gesehen zu haben.

Warum der letzte aus der Reihe fälltDie ersten vier stehen in den Tabellen der Dokumentation und werden von den Isolationsstufen ausdrücklich geregelt. Das Lost Update steht dort nicht — und ist trotzdem der Fehler, der im Betrieb am häufigsten echtes Geld kostet. Abschnitt sechs geht ihm nach.

Die Stufen und was sie versprechen

Der SQL-Standard beschreibt vier Stufen. Er legt dabei fest, welche Anomalien auf einer Stufe nicht auftreten dürfen — nicht, welche auftreten müssen. Ein System darf also strenger sein als gefordert, und genau das nutzen beide Systeme aus.

READ UNCOMMITTED
Schwächste Stufe. Dirty Reads erlaubt.
READ COMMITTED
Keine Dirty Reads. Non-Repeatable Reads und Phantome möglich. Voreinstellung in beiden Systemen.
REPEATABLE READ
Zusätzlich keine Non-Repeatable Reads. Ob Phantome möglich sind, unterscheidet die beiden Systeme — siehe unten.
SERIALIZABLE
Strengste Stufe. Das Ergebnis entspricht einer Ausführung nacheinander.

Die Wahl ist ein Handel: Jede Stufe nach oben kauft Verlässlichkeit und bezahlt sie mit Nebenläufigkeit, mit Wartezeit oder mit Wiederholungen. Es gibt keine Stufe, die nur Vorteile hat — und die Voreinstellung ist bewusst nicht die strengste.

PostgreSQL: vier Namen, drei Stufen

Man kann in PostgreSQL alle vier Standardstufen anfordern, aber intern sind nur drei verschiedene umgesetzt. Die Dokumentation stellt das ausdrücklich fest: Der Modus READ UNCOMMITTED verhält sich wie READ COMMITTED, weil das die einzig sinnvolle Abbildung der Standardstufen auf die Mehrversionen-Architektur ist.

Was das praktisch heißtSET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED wird angenommen und ändert nichts. Wer es setzt, um „schneller zu lesen“, bekommt exakt das Verhalten von vorher — nur steht jetzt eine Zeile im Code, die eine Absicht behauptet, die das System nicht umsetzt. Dirty Reads sind in PostgreSQL gar nicht erreichbar.

Die zweite Abweichung geht in die andere Richtung. Die Dokumentation hält fest, dass PostgreSQLs Umsetzung von REPEATABLE READ keine Phantom Reads zulässt — strenger, als der Standard es verlangt, und laut Standard zulässig, weil höhere Garantien erlaubt sind. Umgesetzt ist die Stufe als das, was in der Literatur Snapshot Isolation heißt.

Auf der Voreinstellung READ COMMITTED sieht eine Abfrage laut Dokumentation einen Schnappschuss zum Zeitpunkt, an dem die Abfrage zu laufen beginnt — nicht die Transaktion. Daraus folgt unmittelbar: Zwei aufeinanderfolgende SELECT-Befehle innerhalb einer Transaktion können verschiedene Daten sehen, wenn dazwischen jemand bestätigt hat. Wer einen Bericht aus mehreren Abfragen zusammensetzt, bekommt auf dieser Stufe kein zusammenhängendes Bild.

Kurzcheck

Ein Auswertungsjob liest in einer Transaktion erst eine Summe aus der Kopftabelle und danach die Einzelposten. Die beiden passen gelegentlich nicht zusammen. Welche Stufe behebt das?

  • REPEATABLE READ — alle Abfragen sehen denselben Schnappschuss
  • READ UNCOMMITTED — dann sieht der Job auch die laufenden Änderungen
  • Keine — das ist ein Fehler in der Abfrage und gehört dort behoben

Treffer. Richtig. Ab dieser Stufe liegt der Schnappschuss auf dem Beginn der Transaktion statt auf dem Beginn jeder einzelnen Abfrage, und alle Lesevorgänge sehen denselben Stand.

SQL Server: derselbe Stufenname, zwei Verhalten

Bei SQL Server ist READ COMMITTED ebenfalls die Voreinstellung — aber was diese Stufe tut, hängt an einer Datenbankeinstellung. Die Dokumentation beschreibt beide Fälle getrennt.

READ_COMMITTED_SNAPSHOT = OFF
Die Engine nimmt gemeinsame Sperren, um andere Transaktionen am Ändern gelesener Zeilen zu hindern. Lesevorgänge können auf Schreiber warten. Voreinstellung bei SQL Server.
READ_COMMITTED_SNAPSHOT = ON
Die Engine arbeitet mit Zeilenversionen und zeigt jeder Anweisung einen konsistenten Schnappschuss zum Anweisungsbeginn. Sperren werden dafür nicht verwendet. Voreinstellung bei Azure SQL Database.
Zwei Instanzen, dieselbe Stufe, anderes VerhaltenDieselbe Anwendung auf einer lokalen Instanz und in Azure SQL Database läuft ohne jede Codeänderung in zwei verschiedenen Nebenläufigkeitsmodellen. Ein Blockierungsproblem, das in der Cloud nie auftrat, kann lokal der Normalfall sein — und umgekehrt verschwindet eine Blockade beim Umzug, ohne dass jemand etwas repariert hätte.

Bei REPEATABLE READ gehen die beiden Systeme auseinander. SQL Server hält auf dieser Stufe gemeinsame Sperren auf allen gelesenen Daten bis zum Ende der Transaktion. Andere Transaktionen können deshalb keine gelesene Zeile ändern — aber sie können neue Zeilen einfügen, die zur Suchbedingung passen. Wiederholt die Transaktion ihre Abfrage, erhält sie die neuen Zeilen: Phantom Reads sind hier also möglich, in PostgreSQL nicht. Die Dokumentation weist zudem darauf hin, dass die Nebenläufigkeit dadurch geringer ist als bei READ COMMITTED, und empfiehlt die Stufe nur, wenn sie wirklich nötig ist.

Daneben kennt SQL Server eine eigene Stufe SNAPSHOT. Sie muss mit ALLOW_SNAPSHOT_ISOLATION freigeschaltet sein, fordert beim Lesen keine Sperren an und blockiert damit keine Schreiber — und wird von ihnen nicht blockiert. Eine bereits mit einer anderen Stufe begonnene Transaktion lässt sich laut Dokumentation nicht nachträglich auf SNAPSHOT umstellen; der Versuch bricht die Transaktion ab.

Der Satz, der den Mythos beendet

Die häufigste Fehlvorstellung im Datenbankbetrieb lautet: Wenn die Anwendung blockiert, nimmt man die Isolationsstufe herunter, dann löst sich das. Auf der Leseseite stimmt das teilweise. Auf der Schreibseite ist es falsch, und die Dokumentation sagt das in einem Satz.

Wörtlich aus der SQL-Server-DokumentationDie Wahl einer Transaktions-Isolationsstufe wirkt sich nicht auf die Sperren aus, die zum Schutz von Datenänderungen genommen werden. Eine Transaktion erhält immer eine exklusive Sperre auf allen Daten, die sie ändert, und hält diese Sperre bis zu ihrem Ende — unabhängig von der gesetzten Isolationsstufe.

Daraus folgt: Wer zwei schreibende Transaktionen auf derselben Zeile hat, löst das Warten durch keine Stufe der Welt. Isolationsstufen regeln, wie stark ein Leser vor den Änderungen anderer geschützt wird. Sie regeln nicht, wie lange ein Schreiber seine Sperre hält — das regelt allein die Dauer seiner Transaktion.

Das gilt auch für den bekanntesten Vertreter dieser Idee. READ UNCOMMITTED hat laut Dokumentation dieselbe Wirkung, als würde man NOLOCK auf alle Tabellen in allen SELECT-Anweisungen einer Transaktion setzen. Was man dafür bekommt, steht im selben Abschnitt: Es lassen sich unbestätigte Änderungen lesen, Werte können sich ändern und Zeilen können im Ergebnis auftauchen oder verschwinden, bevor die Transaktion endet.

Preis der ehrlichen AlternativeDas richtige Gegenmittel gegen blockierende Leser ist Zeilenversionierung — READ_COMMITTED_SNAPSHOT ON oder die SNAPSHOT-Stufe. Sie ist nicht kostenlos: Die alten Zeilenversionen müssen irgendwo liegen, und das geht bei SQL Server zulasten von tempdb. Die Umstellung selbst ist ebenfalls keine Kleinigkeit — beim Setzen von READ_COMMITTED_SNAPSHOT darf laut Dokumentation keine andere Verbindung in der Datenbank offen sein.

Lost Update: der Fehler, den keine Voreinstellung verhindert

Der teuerste Nebenläufigkeitsfehler steht in keiner Anomalien-Tabelle, weil er kein Lesefehler ist, sondern ein Muster: lesen, im Anwendungscode rechnen, zurückschreiben.

Zwei Buchungen, ein verlorener Betrag

Szenario

Zwei Sitzungen buchen gleichzeitig auf dasselbe Konto. Beide arbeiten auf der Voreinstellung READ COMMITTED.

Anforderungen

  • Beide lesen den Kontostand in die Anwendung
  • Beide rechnen dort einen Betrag hinzu
  • Beide schreiben das Ergebnis mit einem vollständigen Wert zurück

Schritte

  1. Sitzung A liest den Stand 1.000 und rechnet +100.
  2. Sitzung B liest — ebenfalls 1.000, denn A hat noch nichts geschrieben — und rechnet +50.
  3. A schreibt 1.100 und bestätigt. B schreibt 1.050 und bestätigt.
  4. Ergebnis: 1.050. Die Buchung von A ist verschwunden, obwohl beide Transaktionen fehlerfrei durchgelaufen sind und beide Erfolg gemeldet haben.

Merksatz: Kein System hat hier einen Fehler gemacht. Beide Transaktionen waren in sich korrekt — der Fehler liegt darin, dass zwischen Lesen und Schreiben eine Rechnung außerhalb der Datenbank stattfand.

Es gibt drei Auswege, und sie kosten Verschiedenes.

Im Befehl rechnen
Statt einen fertigen Wert zu schreiben, die Änderung relativ ausdrücken. Die Engine liest und schreibt dann in einem Schritt, und die exklusive Sperre auf der Zeile deckt beides ab. Billigste Lösung, aber nur für einfache Rechnungen.
Zeile beim Lesen sperren
SELECT ... FOR UPDATE sperrt die gelesenen Zeilen so, als würden sie geändert. Die zweite Sitzung wartet. Funktioniert auch bei komplexer Rechnung — kostet Wartezeit und erhöht die Deadlock-Gefahr.
Höher stufen und wiederholen
Ab REPEATABLE READ bricht die zweite Transaktion mit einem Serialisierungsfehler ab, statt zu überschreiben. Sauberste Trennung — verlangt aber, dass die Anwendung wiederholen kann.
Was PostgreSQL ab `REPEATABLE READ` genau tutFindet eine Transaktion dieser Stufe eine Zielzeile, die eine andere Transaktion nach ihrem Beginn geändert und bestätigt hat, wird sie laut Dokumentation zurückgerollt — mit der Meldung, dass der Zugriff wegen gleichzeitiger Änderung nicht serialisiert werden konnte. Der Lost Update wird damit zu einem sichtbaren Fehler statt zu einem stillen Datenverlust. Das ist der eigentliche Gewinn der höheren Stufe.

Wer höher stuft, muss wiederholen können

Die höheren Stufen sind kein Schalter, den man allein im Betrieb umlegt. Die Dokumentation formuliert die Bedingung unmissverständlich: Anwendungen, die diese Stufe verwenden, müssen darauf vorbereitet sein, Transaktionen wegen Serialisierungsfehlern zu wiederholen.

Praktisch heißt das: Die Anwendung muss den Fehler erkennen, die gesamte Transaktion von vorn beginnen und das eine begrenzte Zahl von Malen tun. Beim zweiten Durchlauf sieht sie die zwischenzeitlich bestätigte Änderung als Teil ihres Ausgangsbildes, sodass kein logischer Konflikt mehr besteht. Erkennen lässt sich der Fall zuverlässig über den SQLSTATE-Wert 40001, den Serialisierungsfehler immer zurückgeben.

Die Einschränkung, die den Aufwand kleiner machtLaut Dokumentation müssen nur ändernde Transaktionen wiederholt werden; reine Lesetransaktionen haben nie Serialisierungskonflikte. Ein Auswertungsjob kann also gefahrlos auf REPEATABLE READ gestellt werden, ohne dass jemand eine Wiederholungslogik bauen muss. Genau das ist der billigste sinnvolle Einsatz der höheren Stufe.

SERIALIZABLE geht darüber hinaus und überwacht zusätzlich Bedingungen, unter denen ein Satz gleichzeitiger Transaktionen zu einem Ergebnis führen würde, das zu keiner Reihenfolge nacheinander passt. PostgreSQL verwendet dazu Prädikatsperren, die im Systemkatalog pg_locks als SIReadLock auftauchen. Die Dokumentation hält eine wichtige Eigenschaft fest: Diese Sperren blockieren nicht und können deshalb an keinem Deadlock beteiligt sein. Bezahlt wird stattdessen mit dem Aufwand der Überwachung und mit den Wiederholungen.

Grenze auch der strengsten StufeSERIALIZABLE verhindert Anomalien, aber nicht jede Fehlermeldung, die bei echter Ausführung nacheinander nicht aufträte. Die Dokumentation nennt den Fall ausdrücklich: Verletzungen einer Eindeutigkeitsbedingung können auch dann auftreten, wenn zuvor geprüft wurde, dass der Schlüssel noch nicht vorhanden ist.

Sperren: was wirklich blockiert — und wie lange

Unter den Stufen liegt der Sperrmechanismus, und dort gibt es zwei Missverständnisse, die beide aus den Namen stammen.

Die Namen mit „ROW“ sind TabellensperrenDie PostgreSQL-Dokumentation warnt ausdrücklich: Alle Tabellen-Sperrmodi sind Sperren auf Tabellenebene, auch wenn ihr Name das Wort ROW enthält — die Namen sind historisch gewachsen. Ein ROW EXCLUSIVE sperrt nicht eine Zeile, sondern die Tabelle in einem bestimmten Modus. Der einzige tatsächliche Unterschied zwischen den Modi ist, mit welchen anderen Modi sie in Konflikt stehen.

Das zweite Missverständnis betrifft die Leser. Zeilensperren beeinträchtigen das Abfragen von Daten laut Dokumentation nicht; sie blockieren ausschließlich Schreiber und andere Sperrende auf derselben Zeile. Ein SELECT bleibt also in PostgreSQL nicht an einem laufenden UPDATE hängen — was erklärt, warum NOLOCK-Denken aus der SQL-Server-Welt hier von vornherein keinen Gegenstand hat.

Die operativ wichtigste Aussage betrifft aber die Zeit. Solange kein Deadlock erkannt wird, wartet eine Transaktion, die eine Sperre sucht, laut Dokumentation unbegrenzt darauf, dass die konfliktierende Sperre freigegeben wird. Es gibt keine Voreinstellung, die das nach einer Weile abbricht.

Die Folgerung, die die Dokumentation selbst ziehtEs ist deshalb eine schlechte Idee, Transaktionen über lange Zeiträume offen zu halten — zum Beispiel, während auf eine Benutzereingabe gewartet wird. Eine Transaktion, die an einem offenen Dialogfenster hängt, hält ihre exklusiven Sperren bis zum Mittagessen des Anwenders, und alles, was dieselben Zeilen braucht, wartet mit.

Deadlock: erkannt, aufgelöst, nicht vorhersagbar

Ein Deadlock ist der Fall, in dem zwei Transaktionen jeweils eine Sperre halten, die die andere braucht. Ohne Eingriff würden beide unbegrenzt warten — siehe oben.

PostgreSQL erkennt solche Situationen laut Dokumentation automatisch und löst sie auf, indem es eine der beteiligten Transaktionen abbricht, sodass die andere durchlaufen kann. Und dann folgt der Satz, der für den Betrieb zählt: Welche Transaktion abgebrochen wird, ist schwer vorherzusagen und sollte nicht vorausgesetzt werden.

Warum das keine Randnotiz istWer ein Skript so baut, dass im Deadlock-Fall „das andere“ abbricht, hat sich auf etwas verlassen, das die Dokumentation ausdrücklich als unzuverlässig bezeichnet. Jede Seite muss mit dem eigenen Abbruch rechnen — und das heißt wieder: wiederholen können.

Wichtig ist auch, dass Deadlocks kein Sonderfall expliziter Sperrbefehle sind. Die Dokumentation zeigt das Gegenteil an einem Beispiel aus zwei ganz gewöhnlichen UPDATE-Anweisungen: Die erste Transaktion ändert Zeile A und dann Zeile B, die zweite Zeile B und dann Zeile A. Keine der beiden hat je einen Sperrbefehl ausgeführt, und trotzdem steht der Verbund.

Genau daraus ergibt sich die Abwehr. Die beste Verteidigung ist laut Dokumentation, Deadlocks zu vermeiden, indem alle Anwendungen Sperren auf mehreren Objekten in einer einheitlichen Reihenfolge anfordern. Hätten im Beispiel beide Transaktionen die Zeilen in derselben Reihenfolge bearbeitet, wäre kein Deadlock entstanden. Ergänzend nennt die Dokumentation, den ersten Zugriff auf ein Objekt gleich im strengsten später benötigten Modus zu nehmen — und, wo sich das vorab nicht sicherstellen lässt, abgebrochene Transaktionen zu wiederholen.

Die Reihenfolge, die das zusammenhält

Anomalie benennen, bevor eine Stufe gewählt wirdPrüfen, ob es überhaupt ein Leseproblem istUmfang der Transaktion so eng wie möglich schneidenSperrreihenfolge über alle Zugriffe vereinheitlichenWiederholung bauen, bevor höher gestuft wird
Anomalie vor Stufe

Schritt zwei ist der, der am meisten Arbeit spart. Blockieren zwei Schreiber einander, hilft keine Stufe — eine Transaktion erhält immer eine exklusive Sperre auf dem, was sie ändert, und hält sie bis zum Ende. Die einzige wirksame Stellschraube ist dann, wie lange diese Transaktion offen bleibt.

Zum Schluss eine Änderung, die der Rollback nicht zurücknimmtNicht alles in einer Datenbank ist transaktional. Die PostgreSQL-Dokumentation hält ausdrücklich fest, dass Änderungen an einer Sequenz — und damit am Zähler einer serial-Spalte — sofort für alle anderen Transaktionen sichtbar sind und nicht zurückgerollt werden, wenn die ändernde Transaktion abbricht. Ein zurückgerolltes INSERT verbraucht seine Nummer trotzdem. Lücken in einer laufenden Nummer sind deshalb normal und kein Hinweis auf verlorene Daten — wer sie als Lückenlosigkeitszusage an die Fachlichkeit verkauft, verspricht etwas, das die Datenbank nie zugesagt hat.
AbgrenzungWie Sperren im Betrieb sichtbar gemacht und eine blockierende Kette aufgelöst wird, steht im Modul über die Diagnose von Datenbankvorfällen. Welche Sperren eine Schemaänderung nimmt, steht im Modul über Schemaänderungen im laufenden Betrieb. Hier ging es um die Frage, welche Garantie eine Transaktion überhaupt gibt — und was sie dafür verlangt.

Jetzt anwenden

Diesen Stoff gibt es als Modul mit bewerteten Entscheidungs-Checks — dieselbe Einführung, danach die Übungen.

Zum Modul →
Quellen & Aktualität6 Primärquellen · zuletzt geprüft:
  1. 01postgresql.org/docs/current/transaction-iso.html
  2. 02postgresql.org/docs/current/explicit-locking.html
  3. 03postgresql.org/docs/current/sql-begin.html
  4. 04postgresql.org/docs/current/tutorial-transactions.html
  5. 05learn.microsoft.com/en-us/sql/t-sql/statements…n-level-transact-sql
  6. 06learn.microsoft.com/en-us/sql/t-sql/statements…t-abort-transact-sql