Transaktionen in SQL: Datenkonsistenz mit BEGIN, COMMIT und ROLLBACK
Eine Überweisung, bei der das Geld abgebucht, aber nie gutgeschrieben wird – genau davor schützen dich Transaktionen. Wie ACID funktioniert und wie du COMMIT, ROLLBACK und Isolationslevel richtig einsetzt.
Stell dir vor, du programmierst eine Banking-App. Ein Nutzer überweist 100 Euro von Konto A auf Konto B. Dafür brauchst du zwei UPDATE-Statements: eines zieht den Betrag ab, eines schreibt ihn gut. Und jetzt die unangenehme Frage: Was passiert, wenn die Datenbank genau zwischen diesen beiden Statements abstürzt?
Ohne Schutzmechanismus ist das Geld weg. Es wurde abgebucht, aber nie gutgeschrieben. Genau für dieses Problem gibt es Transaktionen – und sie gehören zu den wichtigsten Konzepten, die du im Umgang mit Datenbanken lernen kannst.
Was eine Transaktion eigentlich ist
Eine Transaktion ist eine Gruppe von SQL-Anweisungen, die die Datenbank als eine einzige, unteilbare Einheit behandelt. Entweder werden alle Anweisungen ausgeführt – oder keine einzige. Ein Zwischenzustand existiert für andere Nutzer nie.
Du klammerst die Anweisungen dafür mit drei Schlüsselwörtern ein:
BEGIN(oderSTART TRANSACTION) – ab hier zählt alles zur TransaktionCOMMIT– alles verbindlich speichernROLLBACK– alles verwerfen, als wäre nichts passiert
BEGIN;
UPDATE konten SET saldo = saldo - 100 WHERE id = 1;
UPDATE konten SET saldo = saldo + 100 WHERE id = 2;
COMMIT;Stürzt die Datenbank nach dem ersten UPDATE ab, wird beim Neustart automatisch zurückgerollt. Das Geld bleibt auf Konto A. Nichts geht verloren.
ACID: Die vier Garantien
Hinter Transaktionen steckt ein Satz von vier Eigenschaften, abgekürzt als ACID. Das klingt nach Theorie, beschreibt aber sehr praktische Zusagen:
- Atomicity (Atomarität): Alles oder nichts. Halbe Transaktionen gibt es nicht.
- Consistency (Konsistenz): Vor und nach der Transaktion sind alle Regeln der Datenbank erfüllt – Fremdschlüssel,
CHECK-Constraints,NOT NULL. - Isolation (Isolation): Parallel laufende Transaktionen stören sich nicht gegenseitig.
- Durability (Dauerhaftigkeit): Nach einem erfolgreichen
COMMITsind die Daten sicher auf der Platte – auch bei einem Stromausfall eine Sekunde später.
Ein CHECK-Constraint macht die Konsistenz greifbar:
CREATE TABLE konten (
id INTEGER PRIMARY KEY,
inhaber TEXT NOT NULL,
saldo NUMERIC(10,2) NOT NULL CHECK (saldo >= 0)
);Versucht eine Transaktion, ein Konto ins Minus zu ziehen, schlägt das UPDATE fehl – und die gesamte Transaktion lässt sich sauber zurückrollen.
Fehlerbehandlung in der Anwendung
In der Praxis rufst du COMMIT und ROLLBACK selten von Hand auf. Stattdessen übernimmt das dein Datenbanktreiber. In Python mit psycopg reicht ein with-Block:
import psycopg
def ueberweisen(conn, von_id, nach_id, betrag):
with conn.transaction(): # COMMIT am Ende, ROLLBACK bei Exception
with conn.cursor() as cur:
cur.execute(
"UPDATE konten SET saldo = saldo - %s WHERE id = %s",
(betrag, von_id),
)
cur.execute(
"UPDATE konten SET saldo = saldo + %s WHERE id = %s",
(betrag, nach_id),
)
with psycopg.connect("dbname=bank") as conn:
try:
ueberweisen(conn, von_id=1, nach_id=2, betrag=100)
print("Überweisung erfolgreich")
except psycopg.errors.CheckViolation:
print("Nicht genug Deckung – nichts wurde verändert")Der entscheidende Punkt: Fliegt irgendwo im Block eine Exception, rollt der Kontextmanager die komplette Transaktion zurück. Du musst nicht daran denken.
Isolationslevel: Wie stark trennst du parallele Zugriffe?
Das I in ACID ist der Teil, den du selbst justieren kannst. Je strenger die Isolation, desto weniger Anomalien – aber desto mehr Sperren und desto langsamer wird es. SQL kennt vier Stufen:
- READ UNCOMMITTED – du siehst ungespeicherte Änderungen anderer (Dirty Read). Praktisch nie sinnvoll.
- READ COMMITTED – du siehst nur committete Daten. Standard in PostgreSQL.
- REPEATABLE READ – dieselbe Abfrage liefert innerhalb der Transaktion immer dasselbe Ergebnis. Standard in MySQL/InnoDB.
- SERIALIZABLE – das Ergebnis entspricht dem einer streng nacheinander ausgeführten Reihenfolge. Maximal sicher, maximal teuer.
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT SUM(saldo) FROM konten WHERE kunde_id = 42;
-- Andere Transaktionen können dieses Ergebnis nicht unter dir verändern
UPDATE kunden SET gesamtvermoegen = 5000 WHERE id = 42;
COMMIT;Faustregel: Bleib beim Standard deiner Datenbank. Erhöhe die Stufe erst, wenn du eine konkrete Anomalie beobachtest – und rechne dann damit, dass Transaktionen mit einem Serialisierungsfehler abbrechen und wiederholt werden müssen.
Savepoints: Teilweise zurückrollen
Manchmal willst du nicht alles verwerfen, sondern nur einen Teilschritt. Dafür gibt es SAVEPOINT – eine Art Zwischenmarke innerhalb der Transaktion:
BEGIN;
INSERT INTO bestellungen (kunde_id, summe) VALUES (42, 199.00);
SAVEPOINT vor_gutschein;
UPDATE bestellungen SET summe = summe - 20 WHERE kunde_id = 42;
-- Gutschein war ungültig, nur diesen Schritt zurücknehmen
ROLLBACK TO SAVEPOINT vor_gutschein;
COMMIT; -- Die Bestellung bleibt bestehen, der Rabatt nichtDas ist besonders nützlich bei Batch-Verarbeitung: Ein einzelner fehlerhafter Datensatz muss nicht den ganzen Import kippen.
Typische Fehler, die du vermeiden solltest
Ein paar Stolperfallen begegnen einem immer wieder:
- Transaktionen zu lange offen halten. Jede offene Transaktion hält Sperren und blockiert andere. Führe niemals HTTP-Requests oder Nutzer-Eingaben innerhalb einer Transaktion aus.
- Deadlocks durch unterschiedliche Reihenfolge. Sperrt Transaktion A erst Zeile 1, dann Zeile 2, und B umgekehrt, warten beide ewig. Greife auf Zeilen immer in derselben Reihenfolge zu, etwa sortiert nach ID.
- Autocommit übersehen. Viele Treiber committen jedes Statement einzeln. Ohne explizites
BEGINhast du gar keine Transaktion – nur eine Reihe unabhängiger Schreibvorgänge. - DDL in Transaktionen. PostgreSQL kann
CREATE TABLEzurückrollen, MySQL committet dabei implizit. Verlass dich nicht auf einheitliches Verhalten.
Fazit
Transaktionen sind das Sicherheitsnetz deiner Datenbank: Sie sorgen dafür, dass zusammengehörende Änderungen entweder komplett oder gar nicht passieren. Merke dir das Muster BEGIN – Arbeit – COMMIT beziehungsweise ROLLBACK im Fehlerfall, nutze die Kontextmanager deines Treibers statt manueller Aufrufe, und halte Transaktionen so kurz wie möglich. Wenn du das beherzigst, hast du eine ganze Klasse von schwer reproduzierbaren Datenfehlern schon ausgeschlossen, bevor sie entstehen.