Fehler in PostgreSQL können aus vielen Gründen auftreten: Constraint-Verletzungen (NOT NULL, UNIQUE, CHECK), Syntaxfehler, doppelte Werte usw. Wenn du solche Fehler nicht abfängst und behandelst, wird die gesamte äußere Transaktion komplett zurückgesetzt. Für stabile Business-Operationen ist es wichtig, sie richtig zu behandeln.
PL/pgSQL bietet einen mächtigen Mechanismus mit BEGIN ... EXCEPTION ... END-Blöcken, um Fehler in Funktionen und Prozeduren abzufangen und zu behandeln. Sie sind ähnlich wie try-catch in Python oder Java, aber mit Besonderheiten im Kontext von PostgreSQL-Transaktionen.
Wichtiger Punkt:
Jeder BEGIN ... EXCEPTION ... END-Block funktioniert wie ein „virtueller Savepoint“. Wenn eine Exception auftritt, werden alle Änderungen in diesem Block automatisch zurückgesetzt. Das ist der einzige korrekte Weg für einen teilweisen Rollback in PL/pgSQL-Funktionen und -Prozeduren.
Fehlerbehandlungs-Syntax mit EXCEPTION
BEGIN
-- Hauptcode
EXCEPTION
WHEN FEHLERTYP THEN
-- Behandlung eines bestimmten Fehlers
WHEN ANDERER_FEHLERTYP THEN
-- anderer Handler
WHEN OTHERS THEN
-- Behandlung aller anderen Fehler
END;
Illustration in Funktionen/Prozeduren
DO $$
BEGIN
RAISE NOTICE 'Jetzt kommt ein Fehler...';
PERFORM 1 / 0;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Division durch Null abgefangen!';
END;
$$;
Beispiel: Update mit Fehlerbehandlung und Rollback
Angenommen, es gibt eine Bestellungen-Tabelle:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
amount NUMERIC NOT NULL,
status TEXT NOT NULL
);
Wir erstellen eine Funktion, die den Status einer Bestellung aktualisiert und bei Fehler nichts ändert:
CREATE OR REPLACE FUNCTION update_order_status(order_id INT, new_status TEXT)
RETURNS VOID AS $$
BEGIN
BEGIN
UPDATE orders
SET status = new_status
WHERE id = order_id;
-- Fehler simulieren
IF new_status = 'FAIL' THEN
RAISE EXCEPTION 'Fehler wird simuliert!';
END IF;
RAISE NOTICE 'Bestellstatus % aktualisiert', order_id;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Fehler beim Aktualisieren der Bestellung %: %', order_id, SQLERRM;
-- Alle Änderungen im Block werden automatisch zurückgesetzt!
-- Fehler erneut werfen für äußere Behandlung, falls nötig
RAISE;
END;
END;
$$ LANGUAGE plpgsql;
Wie das in Prozeduren mit expliziter Transaktionssteuerung funktioniert
In Prozeduren (CREATE PROCEDURE) kannst du COMMIT, ROLLBACK, SAVEPOINT verwenden, aber du darfst kein ROLLBACK TO SAVEPOINT machen. Wenn du nur einen Teil der Operationen in der Prozedur zurücksetzen willst, nutze weiterhin BEGIN ... EXCEPTION ... END:
CREATE OR REPLACE PROCEDURE pay_order(order_id INT, amount NUMERIC)
LANGUAGE plpgsql
AS $$
BEGIN
-- Die ganze Prozedur kann COMMIT/ROLLBACK nutzen, aber für Rollback eines einzelnen Schritts:
BEGIN
UPDATE accounts
SET balance = balance - amount
WHERE id = (SELECT account_id FROM orders WHERE id = order_id);
-- Fehler
IF amount < 0 THEN
RAISE EXCEPTION 'Betrag darf nicht negativ sein!';
END IF;
UPDATE orders SET status = 'PAID' WHERE id = order_id;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Fehler bei der Zahlungsabwicklung für Bestellung %: %', order_id, SQLERRM;
-- Änderungen in diesem Block werden automatisch zurückgesetzt
END;
COMMIT; -- Transaktion darfst du explizit nur in Prozeduren abschließen!
END;
$$;
Fehler-Logging
Es ist wichtig, Fehler nicht nur abzufangen, sondern sie auch für spätere Analyse zu speichern.
CREATE TABLE error_log (
id SERIAL PRIMARY KEY,
order_id INT,
error_message TEXT,
error_time TIMESTAMP DEFAULT now()
);
In einer Funktion oder Prozedur:
EXCEPTION
WHEN OTHERS THEN
INSERT INTO error_log (order_id, error_message)
VALUES (order_id, SQLERRM);
RAISE NOTICE 'Fehler ins Log eingetragen: %', SQLERRM;
RAISE;
Wichtige Einschränkungen und Besonderheiten in PostgreSQL 17
Innerhalb von Funktionen (CREATE FUNCTION ... ) darfst du keine Transaktionsbefehle (BEGIN, COMMIT, ROLLBACK, SAVEPOINT, ROLLBACK TO SAVEPOINT, RELEASE SAVEPOINT) verwenden! Alle Funktionen laufen komplett im Rahmen der äußeren Transaktion.
Innerhalb von Prozeduren (CREATE PROCEDURE ... ) darfst du explizit SAVEPOINT, RELEASE SAVEPOINT, COMMIT, ROLLBACK schreiben. ABER: ROLLBACK TO SAVEPOINT — VERBOTEN! (Du bekommst einen Syntaxfehler, wenn du ROLLBACK TO SAVEPOINT in einer PL/pgSQL-Prozedur verwendest).
Rollback von „Teilen des Codes“ in Funktionen und Prozeduren macht man mit BEGIN ... EXCEPTION ... END-Blöcken. Wenn ein Fehler auftritt, wird alles im Block automatisch zurückgesetzt und die Ausführung kann weitergehen.
Prozeduren (CREATE PROCEDURE) kannst du nicht in einer Funktion oder per SELECT starten — nur mit dem separaten CALL ...-Befehl.
Wie macht man wirklich einen „teilweisen Rollback“ in PL/pgSQL?
Der einzige funktionierende Weg ist die Fehlerbehandlung mit BEGIN ... EXCEPTION ... END-Blöcken. So ein Block erzeugt automatisch einen Savepoint, und bei Fehler werden die Änderungen im Block zurückgesetzt, ohne den Rest der Prozedur/Funktion zu beeinflussen.
Beispiel mit EXCEPTION (empfohlener Ansatz):
CREATE OR REPLACE PROCEDURE demo_savepoint()
LANGUAGE plpgsql
AS $$
BEGIN
-- Irgendein Code
BEGIN
-- Ein Fehler hier führt nicht zum Rollback der ganzen Prozedur,
-- sondern nur dieses Blocks!
INSERT INTO demo VALUES ('schlechte daten'); -- kann Fehler auslösen
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Fehler behandelt, Änderungen im Block wurden rückgängig gemacht';
END;
-- Hier geht's weiter!
END;
$$;
Beispiel: Batch-Import mit Schutz vor Rollback des gesamten Prozesses
CREATE OR REPLACE PROCEDURE load_big_batch()
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM import_table LOOP
BEGIN
INSERT INTO target_table (col1, col2)
VALUES (rec.col1, rec.col2);
EXCEPTION WHEN OTHERS THEN
INSERT INTO import_errors (err_msg)
VALUES ('Fehler in Zeile: ' || rec.col1 || ': ' || SQLERRM);
-- Änderungen im Block wurden rückgängig gemacht!
END;
END LOOP;
COMMIT; -- erlaubt nur, wenn die Prozedur außerhalb einer expliziten äußeren Transaktion läuft!
END;
$$;
-- Prozedur aufrufen
CALL load_big_batch();
Achtung: Wenn du so eine Prozedur aus einem Client aufrufst, der schon eine Transaktion gestartet hat (z.B. Python mit autocommit=False), führt COMMIT oder SAVEPOINT in der Prozedur zu einem Fehler.
Tipps zum Umgang mit verschachtelten Savepoints und EXCEPTION
- Verwende kein ROLLBACK TO SAVEPOINT in PL/pgSQL! Das gibt einen Syntaxfehler.
- Für teilweisen Rollback immer verschachtelte BEGIN ... EXCEPTION ... END-Blöcke nutzen.
- Denk dran: COMMIT und ROLLBACK in Prozeduren starten die Transaktion neu – du darfst sie nur aufrufen, wenn die Prozedur im autocommit-Modus läuft!
- Logge Fehler in eine eigene Tabelle, damit du Infos über fehlerhafte Zeilen nicht verlierst.
- Wenn eine Business-Operation strikt atomar sein muss (alles-oder-nichts) – mach sie als Funktion, ohne COMMIT/ROLLBACK drin; wenn du Schritt-für-Schritt abarbeiten willst – bau sie als Prozedur.
Beispiel: Import in Batches mit teilweiser Fehlerbehandlung
CREATE OR REPLACE PROCEDURE import_batch()
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM staging_table LOOP
BEGIN
INSERT INTO data_table (data)
VALUES (rec.data);
EXCEPTION
WHEN unique_violation THEN
INSERT INTO import_log (msg)
VALUES ('Duplikat: ' || rec.data);
WHEN OTHERS THEN
INSERT INTO import_log (msg)
VALUES ('Fehler: ' || rec.data || ' — ' || SQLERRM);
END;
END LOOP;
END;
$$;
Das Wichtigste, was du dir für PostgreSQL 17 merken solltest:
In PL/pgSQL-Prozeduren sind SAVEPOINT, RELEASE SAVEPOINT, COMMIT, ROLLBACK erlaubt, aber ROLLBACK TO SAVEPOINT nicht.
„Teilweiser Rollback“ in Funktionen und Prozeduren geht nur mit verschachtelten BEGIN ... EXCEPTION ... END-Blöcken.
Transaktionen steuerst du am besten von außen (über Connection-Parameter und autocommit), und in Prozeduren nutzt du die beschriebenen Fehlerbehandlungs-Mechanismen.
GO TO FULL VERSION