CodeGym /Kursy /SQL SELF /Wywoływanie procedur i funkcji wewnątrz transakcji

Wywoływanie procedur i funkcji wewnątrz transakcji

SQL SELF
Poziom 53 , Lekcja 1
Dostępny

Wywoływanie procedur i funkcji wewnątrz transakcji

W nowoczesnych systemach bazodanowych logika biznesowa często jest realizowana po stronie serwera — za pomocą procedur i funkcji. Pracując z PostgreSQL, ważne jest, żeby rozumieć różnicę między funkcjami a procedurami (szczególnie od wersji 11+, gdzie pojawiły się procedury) i to, jak one współpracują z transakcjami.

Poniżej opowiem Ci najważniejsze fakty o mechanice transakcji, zagnieżdżonych wywołaniach i częściowym rollbacku zmian w procedurach/funkcjach PostgreSQL 17, zgodnie z oficjalną dokumentacją i aktualnymi ograniczeniami.

Kluczowe pojęcia: funkcje vs procedury

Funkcja (CREATE FUNCTION) — zawsze działa w ramach jednej zewnętrznej transakcji; wewnątrz funkcji nie można używać jawnych poleceń transakcyjnych (BEGIN, COMMIT, ROLLBACK, SAVEPOINT).

  • Wszelkie zmiany są zatwierdzane albo cofane tylko na poziomie zewnętrznej transakcji.
  • Do „częściowego rollbacku” wewnątrz funkcji używa się BEGIN ... EXCEPTION ... END, ale to nie pozwala robić commitów wewnątrz funkcji.

Procedura (CREATE PROCEDURE) — pojawiła się, żeby zarządzać transakcjami bezpośrednio na serwerze (np. robić częściowe commity, rollbacki etapów itd.).

  • W procedurach (PL/pgSQL) można używać COMMIT, ROLLBACK, SAVEPOINT, RELEASE SAVEPOINT.
  • WAŻNE: nie można używać ROLLBACK TO SAVEPOINT w procedurze PL/pgSQL (wyrzuci błąd składni).
  • Procedury można wywoływać tylko osobnym poleceniem SQL CALL ..., a nie przez SELECT ani wewnątrz innych funkcji.

Jak wywołać jedną procedurę/funkcję z innej?

Funkcje „przezroczysto” wywołują inne funkcje przez zwykłe odwołanie po nazwie:

-- Przykład: funkcja do obliczania rabatu
CREATE OR REPLACE FUNCTION calculate_discount(order_total NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
    IF order_total >= 100 THEN
        RETURN order_total * 0.1;
    ELSE
        RETURN 0;
    END IF;
END;
$$ LANGUAGE plpgsql;

-- Funkcja obsługi zamówienia wywołuje inną funkcję
CREATE OR REPLACE FUNCTION process_order(order_id INT, order_total NUMERIC)
RETURNS VOID AS $$
DECLARE
    discount NUMERIC;
BEGIN
    discount := calculate_discount(order_total);
    RAISE NOTICE 'Rabat: %', discount;
    INSERT INTO orders_log (order_id, order_total, discount)
    VALUES (order_id, order_total, discount);
END;
$$ LANGUAGE plpgsql;

Wszystko wykonuje się w ramach jednej zewnętrznej transakcji! Błąd w dowolnej funkcji spowoduje rollback wszystkich zmian.

Wywoływanie procedur i zagnieżdżone transakcje

Procedury można wywoływać wewnątrz innych procedur przez polecenie CALL ... (w PostgreSQL 17 można mieć stos wywołań CALL proc1() -> CALL proc2()), ale zasady transakcji pozostają takie same:

  • Polecenia transakcyjne (COMMIT, ROLLBACK, SAVEPOINT, RELEASE SAVEPOINT) są dostępne tylko na najwyższym poziomie procedur.
  • Jeśli procedura z zarządzaniem transakcjami jest wywoływana wewnątrz już aktywnej jawnej transakcji (np. przez klienta bez autocommitu), próba wykonania COMMIT/SAVEPOINT wyrzuci błąd.
WAŻNE:

procedur nie można uruchamiać wewnątrz funkcji ani anonimowych bloków (DO ...). Tylko osobnym poleceniem CALL

Przykład procedury z zarządzaniem transakcjami

-- Procedura z etapowym commitem (działa tylko w trybie autocommit połączenia)
CREATE PROCEDURE process_batch_orders()
LANGUAGE plpgsql
AS $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN SELECT order_id, order_total FROM incoming_orders LOOP
        BEGIN
            -- Zapisujemy każdą partię danych osobno
            INSERT INTO orders (order_id, total) VALUES (rec.order_id, rec.order_total);
        EXCEPTION WHEN OTHERS THEN
            INSERT INTO order_errors(order_id, err_text) VALUES (rec.order_id, SQLERRM);
        END;
        COMMIT;
    END LOOP;
END;
$$;

-- Wywołanie procedury
CALL process_batch_orders();

Po każdym COMMIT automatycznie zaczyna się nowa transakcja.

Częściowy rollback (savepoint-like behavior) w PL/pgSQL

PL/pgSQL (zarówno w funkcjach, jak i procedurach) nie obsługuje polecenia ROLLBACK TO SAVEPOINT.

Do rollbacku zmian części kodu używa się tylko bloku BEGIN ... EXCEPTION ... END:

BEGIN
    -- jakieś działania
    BEGIN
        -- potencjalnie błędna operacja
    EXCEPTION WHEN OTHERS THEN
        -- wszystkie zmiany tego bloku zostaną cofnięte
        RAISE NOTICE 'Rollback wewnątrz bloku!';
    END;
END;

W procedurach można też używać SAVEPOINT i RELEASE SAVEPOINT, ale nie ROLLBACK TO SAVEPOINT. Ich sens to rozdzielanie etapów, ale zarządzać nimi można tylko przez obsługę wyjątków.

Ograniczenia i dobre praktyki

  1. Funkcje — tylko operacje atomowe: wszystko albo nic. Jeśli coś pójdzie nie tak — wszystkie zmiany zostaną cofnięte.
  2. Procedury — tylko przez CALL: i tylko osobnym poleceniem SQL, nie z SELECT/funkcji. Zagnieżdżone zarządzanie transakcjami jest możliwe, ale tylko zgodnie z ograniczeniami PL/pgSQL.
  3. Częściowy rollback — tylko przez EXCEPTION: oficjalnie zalecany i wspierany sposób na częściowy rollback (odpowiednik SAVEPOINT).
  4. Zagnieżdżone procedury mogą zarządzać transakcjami tylko przy wywołaniu przez CALL: w innym przypadku będzie błąd.

Pytania o współpracę logiki i transakcji

Czy mogę zrobić „zagnieżdżoną” transakcję wewnątrz funkcji?

Nie. Wszystko działa w jednej transakcji. Do częściowego rollbacku — tylko bloki EXCEPTION.

Czy mogę robić COMMIT/ROLLBACK wewnątrz funkcji albo anonimowego bloku?

Nie, to błąd składni. Użyj procedur.

Czy można wywołać procedurę z funkcji?

Nie, tylko poleceniem CALL. Z funkcji/SELECT — nie da się.

Czy można w procedurze zrobić ROLLBACK TO SAVEPOINT?

Nie! W PL/pgSQL to zabronione. Używaj bloków EXCEPTION.

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