Stell dir vor, du schreibst eine App für einen Online-Shop und beim Bezahlen einer Bestellung musst du:
- Geld von der Karte des Kunden reservieren.
- Die Anzahl des Produkts im Lager verringern.
- Einen Eintrag über die erfolgreiche Transaktion erstellen.
Was passiert, wenn mitten in diesen Aktionen etwas schiefgeht? Zum Beispiel ist das Produkt im Lager ausverkauft, nachdem das Geld reserviert wurde, aber bevor der Bestelleintrag erstellt wurde? Dann läuft alles schief: Das Geld "hängt fest", die Bestellung ist nicht abgeschlossen und dein Server bekommt tonnenweise wütende Mails (und vielleicht sogar Klagen).
Transaktionen helfen genau, solche Situationen zu vermeiden. Sie erlauben es, mehrere Operationen zu einer "atomaren" Einheit zusammenzufassen. Das ist wie der "Undo"-Button im Texteditor: Wenn was schiefgeht, einfach zum Anfang zurückspringen.
Wie sorgen Transaktionen für Datenintegrität?
Transaktionen basieren auf dem ACID-Konzept:
- Atomarität (Atomicity) — Alle Operationen innerhalb der Transaktion werden entweder komplett ausgeführt oder gar nicht. "Alles oder nichts".
- Konsistenz (Consistency) — Die Daten bleiben vor und nach der Transaktion in einem konsistenten Zustand.
- Isolation (Isolation) — Eine Transaktion stört die anderen nicht.
- Dauerhaftigkeit (Durability) — Wenn die Transaktion abgeschlossen ist, bleibt das Ergebnis auch bei einem Systemabsturz erhalten.
Warum erzähle ich das nochmal? Weil das das Ideal ist, zu dem alle streben. Und... das selten wirklich erreichbar ist. Wenn wir später im Kurs nochmal zu Transaktionen zurückkommen, wirst du sehen, dass wir auf manche ACID-Prinzipien verzichten müssen.
Also genieße die Zeit, in der Transaktionen so einfach und schön sind. Und lass uns zu den Beispielen kommen!
Beispiel für die Verwendung von Transaktionen
Schauen wir uns das Szenario an, einen Studenten hinzuzufügen und ihn für einen Kurs anzumelden.
Angenommen, wir arbeiten mit einer Uni-Datenbank. Es gibt externe Teilnehmer für unsere Kurse. Wenn im Kurs Platz ist, registrieren wir so einen Teilnehmer als Studenten (temporär) und melden ihn für den Kurs an. So läuft das ab.
Beim Hinzufügen eines neuen Studenten in die Datenbank und seiner Anmeldung für einen Kurs müssen wir:
- Einen Eintrag in die Tabelle
studentshinzufügen. - Einen Eintrag in der Tabelle
enrollmentserstellen, der den Studenten mit dem Kurs verbindet.
Wenn etwas schiefgeht (zum Beispiel ist der Kurs schon voll), müssen wir die Operation zurückrollen, damit die Daten nicht zwischen den Tabellen auseinanderlaufen. So geht das:
-- Start der Transaktion
BEGIN;
-- Schritt 1: Student hinzufügen
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Männlich')
RETURNING id;
-- Angenommen, es kommt id = 10 zurück
-- Schritt 2: Für den Kurs anmelden
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 5);
-- Alles erfolgreich? Änderungen speichern
COMMIT;
Was passiert, wenn ein Fehler auftritt?
Plötzlich gibt es einen Fehler bei der Kursanmeldung: Zum Beispiel existiert der Kurs nicht. Wenn du die Transaktion vergisst, bleibt der Student in der Tabelle students, aber in enrollments gibt es keinen Eintrag. Das zerstört die Datenintegrität. Um das zu vermeiden, können wir ROLLBACK benutzen.
-- Start der Transaktion
BEGIN;
-- Schritt 1: Student hinzufügen
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Männlich')
RETURNING id;
-- Schritt 2: Versuch, ihn für den Kurs anzumelden
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 999); -- Fehler: Kurs mit id = 999 existiert nicht!
-- Alle Änderungen zurückrollen
ROLLBACK;
Am Ende wird keine der Operationen ausgeführt und die Datenbank bleibt wie vor der Transaktion.
Verwendung von SAVEPOINT zur Kontrolle
Jetzt stell dir ein komplexeres Szenario vor. Du willst mehrere Operationen durchführen, aber an einer bestimmten Stelle nur bis zu einem bestimmten Punkt zurückrollen, nicht den ganzen Prozess abbrechen.
Lass uns eine schrittweise Anmeldung eines Studenten umsetzen
-- Start der Transaktion
BEGIN;
-- Student hinzufügen
SAVEPOINT add_student; -- Speicherpunkt setzen
INSERT INTO students (name, age, gender)
VALUES ('Anna Song', 22, 'Weiblich');
-- Für den ersten Kurs anmelden
SAVEPOINT enroll_course_1; -- Noch ein Speicherpunkt
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 5);
-- Für den zweiten Kurs anmelden (hier Fehler)
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 999); -- Fehler!
-- Nur bis zum letzten Speicherpunkt zurückrollen
ROLLBACK TO enroll_course_1;
-- Prozess fortsetzen
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 6);
-- Änderungen speichern
COMMIT;
So verhindern Fehler in einem Teil des Prozesses, dass Daten in anderen Teilen verloren gehen.
Überprüfung auf Änderungen
Wenn ein SQL-Query etwas ändert, kann man prüfen, ob wirklich etwas geändert wurde oder nicht.
Es kann ja sein, dass wir ein DELETE gemacht haben, aber keine Zeile hat auf das WHERE gepasst. Oder wir haben ein UPDATE gemacht, aber die Daten waren schon geändert und es ist faktisch nichts passiert.
Dafür gibt es die spezielle Systemvariable FOUND. Sie zeigt an, ob beim letzten SQL-Query Zeilen betroffen waren:
FOUND = TRUE— Der Query hat etwas aktualisiert/gelöscht;FOUND = FALSE— Nichts wurde gelöscht oder geändert.
Mit einem normalen SELECT funktioniert das nicht, nur um Änderungen zu tracken.
Praktische Anwendung: Zahlungsabwicklung
Transaktionen sind besonders nützlich in Finanzanwendungen. Lass uns nochmal ein System anschauen, das Geld von einem Konto auf ein anderes überweisen soll.
-- Start der Transaktion
BEGIN;
-- Schritt 1: Geld vom ersten Konto abziehen
UPDATE accounts
SET balance = balance - 100
WHERE id = 1 AND balance >= 100;
-- Schritt 2: Prüfen, ob die Operation erfolgreich war (Zeilen wurden geändert)
IF NOT FOUND THEN
ROLLBACK; -- Zurückrollen, wenn nicht genug Geld da ist
RAISE EXCEPTION 'Nicht genug Geld!'; -- Fehler! Exception werfen
END IF;
-- Schritt 3: Geld auf das zweite Konto buchen
UPDATE accounts
SET balance = balance + 100
WHERE id = 2;
-- Transaktion speichern
COMMIT;
Hier: Wenn der Kunde versucht, mehr Geld zu überweisen, als auf seinem Konto ist, wird die Transaktion zurückgerollt und die Datenbank bleibt nicht im "hängenden" Zustand.
Besonderheiten und typische Fehler
COMMIT vergessen: Wenn du am Ende der Transaktion COMMIT vergisst, "wartet" die Datenbank und die Änderungen werden nicht gespeichert.
WHERE vergessen: Daten aktualisieren oder löschen ohne Bedingung kann katastrophale Folgen haben. Zum Beispiel löscht DELETE FROM students ohne WHERE alle Studenten.
Lange Transaktionen: Wenn eine Transaktion zu lange offen bleibt, kann sie den Zugriff auf Daten blockieren und zu Performance-Problemen führen. Beende Transaktionen (COMMIT oder ROLLBACK) immer so schnell wie möglich.
Transaktionen sind dein bester Freund, wenn es um Datenintegrität geht. Sie helfen, Inkonsistenzen zu vermeiden, besonders in komplexen Szenarien wie User-Registrierung, Zahlungsabwicklung oder Updates von verknüpften Tabellen. Wenn du die Befehle BEGIN, COMMIT, ROLLBACK und SAVEPOINT beherrschst, kannst du viel robustere und sicherere Anwendungen bauen.
GO TO FULL VERSION