CodeGym /Kursy /SQL SELF /Obsługa błędów i transakcji w triggerach: EXCEPTION, RAIS...

Obsługa błędów i transakcji w triggerach: EXCEPTION, RAISE

SQL SELF
Poziom 58 , Lekcja 3
Dostępny

Obsługa błędów i transakcji w triggerach: EXCEPTION, RAISE

Czasem triggery zachowują się nieprzewidywalnie i może to być związane z:

  • Błędem logicznym w logice funkcji podpiętej do triggera.
  • Naruszeniem ograniczeń bazy danych (np. naruszenie unikalności albo niezgodność typów danych).
  • Problemami w transakcjach, kiedy trigger powoduje rollback zmian przez błąd.
  • Rekursją, jeśli trigger wywołuje sam siebie (często przypadkowo).

Żeby zapobiegać takim problemom, PostgreSQL daje możliwość obsługi błędów wewnątrz triggerów i ich funkcji. Te narzędzia to bloki EXCEPTION i operator RAISE, które dziś ogarniemy na przykładach.

Obsługa błędów przez blok EXCEPTION

Blok EXCEPTION pozwala nam przechwycić błędy i wykonać jakiś kod do ich obsługi. To trochę jak try-catch w językach programowania, takich jak Python czy Java.

Blok EXCEPTION używa się w funkcjach PL/pgSQL tak:

BEGIN
    -- Główny kod funkcji
EXCEPTION
    WHEN <typ_bledu> THEN
        -- Kod obsługi błędu
END;

Gdzie <typ_bledu> — to konkretny błąd albo grupa błędów, które chcesz obsłużyć (np. unique_violation, division_by_zero itd.).

Przykład: logowanie błędów w triggerach

Wyobraź sobie, że mamy tabelę logs, gdzie chcemy zapisywać błędy pojawiające się przy insercie do tabeli students. Oto przykład:

Tworzymy tabelę do logów

CREATE TABLE logs (
    id SERIAL PRIMARY KEY,
    error_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    error_message TEXT
);

Tworzymy funkcję z obsługą błędów

CREATE OR REPLACE FUNCTION track_insert_errors()
RETURNS TRIGGER AS $$
BEGIN
    -- Próbujemy wykonać główny kod
    BEGIN
        -- Przykład "błędnej" akcji: dzielenie przez 0
        PERFORM 1 / (NEW.some_value - NEW.some_value);
    EXCEPTION
        WHEN division_by_zero THEN
            -- Jeśli był błąd dzielenia przez 0, zapisujemy go do logów
            INSERT INTO logs (error_message) VALUES ('Błąd dzielenia przez 0 przy insercie do students');
    END;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Tworzymy trigger

CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION track_insert_errors();

Teraz, jeśli przy insercie do tabeli students pojawi się błąd dzielenia przez zero, zostanie on obsłużony, a info o nim zapisze się do tabeli logs.

Użycie RAISE do diagnostyki i debugowania

Operator RAISE pozwala wypisywać komunikaty o ostrzeżeniach, błędach albo info do debugowania. To mega przydatne, gdy próbujesz ogarnąć, jak działa (albo nie działa!) twój trigger.

Typy komunikatów RAISE:

  1. DEBUG — komunikat do debugowania.
  2. NOTICE — zwykły komunikat informacyjny.
  3. WARNING — ostrzeżenie.
  4. EXCEPTION — komunikat o błędzie, który kończy działanie funkcji.

Składnia RAISE:

RAISE <typ_komunikatu> 'Komunikat';

Możesz też przekazywać wartości zmiennych:

RAISE NOTICE 'Wartość NEW.id = %', NEW.id;

Przykład: debugowanie wartości w triggerze

Załóżmy, że pojawia się błąd przy update w tabeli students i chcesz wiedzieć, jakie wartości NEW i OLD powodują problem. Do tego użyjemy RAISE:

CREATE OR REPLACE FUNCTION debug_student_update()
RETURNS TRIGGER AS $$
BEGIN
    RAISE NOTICE 'OLD.id = %, NEW.id = %', OLD.id, NEW.id;

    -- Przykład warunku powodującego błąd:
    IF NEW.some_field IS NULL THEN
        RAISE EXCEPTION 'Pole some_field nie może być NULL';
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER after_update_students
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION debug_student_update();

Teraz przy każdym update rekordu zobaczysz wartości OLD i NEW, a także dostaniesz jasny komunikat o błędzie, jeśli się pojawi.

Transakcje w triggerach

Triggery wykonują się w kontekście transakcji. To znaczy, że jeśli gdziekolwiek w triggerze albo jego funkcji pojawi się błąd, cała transakcja zostanie cofnięta. To fajnie chroni bazę przed częściowymi zmianami.

Ale to zachowanie czasem powoduje problemy:

  • Jeśli błąd w triggerze jest przez złe dane, to fajnie byłoby cofnąć tylko część akcji.
  • Trzeba pamiętać, że rollback transakcji obejmuje nie tylko trigger, ale całą operację, która go wywołała.

Przykład: użycie transakcji w triggerze

Dla przykładu, wyobraź sobie, że chcemy wykonać jakąś logikę biznesową, która obejmuje dwie akcje: update w tabeli students i zapis loga do logs. Jeśli jedna z tych akcji się nie uda, cała transakcja się cofa.

CREATE OR REPLACE FUNCTION transactional_student_update()
RETURNS TRIGGER AS $$
BEGIN
    -- Logowanie próby update
    INSERT INTO logs (error_message) VALUES ('Próba update studenta z id ' || NEW.id);

    -- Sprawdzamy warunki biznesowe
    IF NEW.some_value IS NULL THEN
        RAISE EXCEPTION 'Pole some_value nie może być NULL';
    END IF;

    -- Jeśli wszystko OK, zwracamy NEW
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER before_update_students
BEFORE UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION transactional_student_update();

Typowe błędy przy pracy z triggerami i jak ich unikać

Często spotykane błędy programistów:

Rekursywne triggery. To się dzieje, gdy trigger robi zmiany, które znowu go wywołują. Przykład rozwiązania: użyj warunku WHEN albo dodaj flagę, żeby nie wywoływać się ponownie.

Rollback całej transakcji przez błędy. Często to nie jest pożądane, jeśli trigger nie jest bezpośrednio związany z głównymi danymi. Przykład rozwiązania: mądrze używaj bloków EXCEPTION.

Za dużo info do debugowania. Zaśmieca logi i utrudnia analizę. Przykład rozwiązania: używaj RAISE tylko podczas developmentu i testów.

Spadek wydajności. Złożone triggery mogą spowolnić operacje INSERT, UPDATE albo DELETE. Przykład rozwiązania: minimalizuj logikę triggera i unikaj zapytań, które mocno obciążają bazę.

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