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 SAVEPOINTw procedurze PL/pgSQL (wyrzuci błąd składni). - Procedury można wywoływać tylko osobnym poleceniem SQL
CALL ..., a nie przezSELECTani 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/SAVEPOINTwyrzuci błąd.
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
- Funkcje — tylko operacje atomowe: wszystko albo nic. Jeśli coś pójdzie nie tak — wszystkie zmiany zostaną cofnięte.
- 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.
- Częściowy rollback — tylko przez EXCEPTION: oficjalnie zalecany i wspierany sposób na częściowy rollback (odpowiednik SAVEPOINT).
- 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.
GO TO FULL VERSION