Manchmal verhalten sich Trigger ziemlich unvorhersehbar, und das kann an Folgendem liegen:
- Logikfehler in der Funktion, die am Trigger hängt.
- Verletzung von Datenbank-Constraints (zum Beispiel Unique-Verletzung oder falsche Datentypen).
- Probleme mit Transaktionen, wenn der Trigger wegen eines Fehlers ein Rollback auslöst.
- Rekursion, wenn der Trigger sich selbst wieder auslöst (oft aus Versehen).
Um solche Probleme zu vermeiden, bietet PostgreSQL die Möglichkeit, Fehler innerhalb von Triggern und deren Funktionen zu behandeln. Diese Tools sind die EXCEPTION-Blöcke und das RAISE-Statement, die wir uns heute anhand von Beispielen anschauen.
Fehlerbehandlung mit dem EXCEPTION-Block
Mit dem EXCEPTION-Block kannst du Fehler abfangen und eigenen Code zur Fehlerbehandlung ausführen. Das ist ähnlich wie try-catch in Programmiersprachen wie Python oder Java.
Der EXCEPTION-Block wird in PL/pgSQL-Funktionen so verwendet:
BEGIN
-- Hauptcode der Funktion
EXCEPTION
WHEN <fehler_typ> THEN
-- Fehlerbehandlungscode
END;
Wobei <fehler_typ> ein bestimmter Fehler oder eine Fehlergruppe ist, die du behandeln willst (zum Beispiel unique_violation, division_by_zero usw.).
Beispiel: Fehler-Logging in Triggern
Stell dir vor, wir haben eine Tabelle logs, in die wir Fehler schreiben wollen, die beim Einfügen von Daten in die Tabelle students auftreten. Hier ein Beispiel:
Wir erstellen eine Tabelle für Logs
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
error_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
error_message TEXT
);
Wir erstellen eine Funktion mit Fehlerbehandlung
CREATE OR REPLACE FUNCTION track_insert_errors()
RETURNS TRIGGER AS $$
BEGIN
-- Wir versuchen, den Hauptcode auszuführen
BEGIN
-- Beispiel für eine "fehlerhafte" Aktion: Division durch 0
PERFORM 1 / (NEW.some_value - NEW.some_value);
EXCEPTION
WHEN division_by_zero THEN
-- Wenn ein Division-durch-Null-Fehler auftritt, loggen wir ihn
INSERT INTO logs (error_message) VALUES ('Fehler: Division durch 0 beim Einfügen in students');
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Wir erstellen einen Trigger
CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION track_insert_errors();
Jetzt wird, wenn beim Einfügen in die Tabelle students ein Division-durch-Null-Fehler passiert, dieser abgefangen und die Info dazu in die Tabelle logs geschrieben.
RAISE für Diagnose und Debugging verwenden
Mit dem RAISE-Statement kannst du Nachrichten zu Warnungen, Fehlern oder Debug-Infos ausgeben. Das ist mega praktisch, wenn du rausfinden willst, wie dein Trigger funktioniert (oder eben nicht funktioniert!).
Typen von RAISE-Nachrichten:
DEBUG— Debug-Nachricht.NOTICE— normale Info-Nachricht.WARNING— Warnung.EXCEPTION— Fehlermeldung, die die Funktion abbricht.
Syntax von RAISE:
RAISE <nachrichten_typ> 'Nachricht';
Du kannst auch Variablenwerte übergeben:
RAISE NOTICE 'Wert von NEW.id = %', NEW.id;
Beispiel: Debugging von Werten im Trigger
Angenommen, beim Update der Tabelle students gibt es einen Fehler und du willst wissen, welche Werte NEW und OLD das Problem verursachen. Dafür nutzen wir RAISE:
CREATE OR REPLACE FUNCTION debug_student_update()
RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE 'OLD.id = %, NEW.id = %', OLD.id, NEW.id;
-- Beispiel für eine Bedingung, die einen Fehler auslöst:
IF NEW.some_field IS NULL THEN
RAISE EXCEPTION 'Feld some_field darf nicht NULL sein';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_update_students
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION debug_student_update();
Jetzt siehst du bei jedem Update die Werte von OLD und NEW und bekommst eine verständliche Fehlermeldung, falls was schiefgeht.
Transaktionen in Triggern
Trigger laufen immer im Kontext einer Transaktion. Das heißt, wenn irgendwo im Trigger oder in seiner Funktion ein Fehler auftritt, wird die ganze Transaktion zurückgerollt. Das schützt die Datenbank elegant vor halbfertigen Änderungen.
Dieses Verhalten kann aber manchmal auch nerven:
- Wenn der Fehler im Trigger durch falsche Daten ausgelöst wird, wäre es manchmal besser, nur einen Teil der Aktionen zurückzurollen.
- Du musst wissen, dass ein Rollback nicht nur den Trigger betrifft, sondern die ganze Operation, die den Trigger ausgelöst hat.
Beispiel: Transaktionen im Trigger nutzen
Zur Veranschaulichung: Stell dir vor, wir wollen eine Business-Logik, die zwei Aktionen umfasst: Update der Tabelle students und Logging in logs. Wenn eine der Aktionen fehlschlägt, wird die ganze Transaktion zurückgerollt.
CREATE OR REPLACE FUNCTION transactional_student_update()
RETURNS TRIGGER AS $$
BEGIN
-- Logging des Update-Versuchs
INSERT INTO logs (error_message) VALUES ('Update-Versuch für Student mit id ' || NEW.id);
-- Wir prüfen Business-Regeln
IF NEW.some_value IS NULL THEN
RAISE EXCEPTION 'Feld some_value darf nicht NULL sein';
END IF;
-- Wenn alles passt, geben wir NEW zurück
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_update_students
BEFORE UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION transactional_student_update();
Typische Fehler bei Triggern und wie man sie vermeidet
Häufige Fehler von Entwicklern:
Rekursive Trigger. Das passiert, wenn ein Trigger Änderungen macht, die ihn selbst wieder auslösen. Lösung: Bedingung mit WHEN nutzen oder ein Flag setzen, um Wiederholungen zu verhindern.
Rollback der ganzen Transaktion wegen Fehlern. Das ist oft unerwünscht, wenn der Trigger nicht direkt mit den Hauptdaten zu tun hat. Lösung: EXCEPTION-Blöcke clever einsetzen.
Zu viele Debug-Infos. Das macht die Logs unübersichtlich und die Analyse schwer. Lösung: RAISE nur während Entwicklung und Tests nutzen.
Performance-Probleme. Komplexe Trigger können INSERT-, UPDATE- oder DELETE-Operationen verlangsamen. Lösung: Trigger-Logik so schlank wie möglich halten und keine schweren Queries drin machen.
GO TO FULL VERSION