CodeGym /Kurse /SQL SELF /Fehler- und Transaktionsbehandlung in Triggern: EXCEPTION...

Fehler- und Transaktionsbehandlung in Triggern: EXCEPTION, RAISE

SQL SELF
Level 58 , Lektion 3
Verfügbar

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:

  1. DEBUG — Debug-Nachricht.
  2. NOTICE — normale Info-Nachricht.
  3. WARNING — Warnung.
  4. 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.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION