postgres stored procedure pl pgSQL transaction managament - ghdrako/doc_snipets GitHub Wiki
- https://www.postgresql.org/docs/current/plpgsql-transactions.html
- https://www.postgresql.fastware.com/postgresql-insider-mgr-sto-prc
- https://www.cybertec-postgresql.com/en/subtransactions-and-performance-in-postgresql/
Nie można zagnieżdżać transakcji, ponieważ PostgreSQL nie zna podtransakcji.
CREATE TABLE test1 (a int) ;
CREATE OR REPLACE PROCEDURE transaction_test1()
LANGUAGE plpgsql
AS $$
BEGIN
FOR i IN 0..5 LOOP
INSERT INTO test1 (a) VALUES (i);
IF i % 2 = 0 THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
END LOOP;
END
$$;
CALL transaction_test1();
BEGIN ; CALL transaction_test1() ;
ERROR: invalid transaction termination
CONTEXTE : PL/pgSQL function transaction_test1() line 6 at COMMIT
Nie można jednocześnie używać klauzuli EXCEPTION
i kontroli transakcyjnej.
BEGIN
BEGIN
INSERT INTO test1 (a) VALUES (1);
COMMIT;
INSERT INTO test1 (a) VALUES (1/0);
COMMIT;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'caught division_by_zero';
END;
END;
$$;
ERREUR: cannot commit while a subtransaction is active
CONTEXTE : fonction PL/pgSQL inline_code_block, ligne 5 à COMMIT
Use CALL
to execute the stored procedure.
If CALL is executed in a transaction block, then the called procedure cannot execute transaction control statements. Czyli nie moze zawierac commit-a.
Transaction control is only possible in CALL
or DO
invocations from the top level or nested CALL or DO invocations without any other intervening command. For example, if the call stack is CALL proc1() → CALL proc2() → CALL proc3()
, then the second and third procedures can perform transaction control actions. But if the call stack is CALL proc1() → SELECT func2() → CALL proc3()
, then the last procedure cannot do transaction control, because of the SELECT in between.
call proc_with_commit(); -- ok
begin; -- synonim start transaction
call proc_with_commit(); -- error - not supported nested transaction
end; -- synonnym commit
begin; -- synonim start transaction
call proc_without_commit(); -- ok
end; -- synonnym commit
Transaction control
CREATE PROCEDURE proc2()
LANGUAGE plpgsql
AS $$
BEGIN
FOR idx IN 1..100 LOOP
INSERT INTO my_tbl(col_num) VALUES(idx);
IF idx % 10 = 0 THEN A
COMMIT; B
ELSE
ROLLBACK; C
END IF;
END LOOP;
END
$$;
CALL proc2();
Call (nest) another stored procedure in a stored procedure.
CREATE PROCEDURE proc3() LANGUAGE plpgsql AS $$
BEGIN
INSERT INTO my_tbl VALUES(1); A
CALL proc4();
INSERT INTO my_tbl VALUES(4); D
COMMIT;
END;
$$;
CREATE PROCEDURE proc4() LANGUAGE plpgsql AS $$
BEGIN
INSERT INTO my_tbl VALUES(2); B
ROLLBACK;
INSERT INTO my_tbl VALUES(3); C
END;
$$;
Restrictions on transaction control
COMMIT/ROLLBACK
cannot be written between BEGIN
and EXCEPTION
. Instead, use EXCEPTION
to make necessary processing into a subblock from BEGIN
to END
, and write COMMIT/ROLLBACK
outside the subblock.
It is then impossible to ensure that “all changes to persistent database state within the block are rolled back” because what is committed (made visible to others) cannot be rolled-back. And that’s the main goal of intermediate commits. This impossibility is implemented with “ ERROR: cannot commit while a subtransaction is active” in spi.c:
From postgres documentation: Under the hood, a block with exception handlers forms a subtransaction, which means that transactions cannot be ended inside such a block.
CREATE OR REPLACE PROCEDURE update_salary_transaction(
emp_id_param INTEGER,
new_salary_param NUMERIC
)
LANGUAGE plpgsql
AS $$
DECLARE
current_salary NUMERIC;
BEGIN
-- Getting the current salary and putting that value in the variable current_salary
SELECT emp_salary INTO current_salary
FROM Employees
WHERE emp_id = emp_id_param;
-- Check if the new salary is different from the current salary (Transaction Control)
IF current_salary = new_salary_param THEN
-- If the new salary is the same, we will raise a notice and not perform the update
RAISE NOTICE 'New salary is the same as the current salary. No update needed.';
ELSE
-- Updating the salary
UPDATE Employees
SET emp_salary = new_salary_param
WHERE emp_id = emp_id_param;
END IF;
COMMIT;
RAISE NOTICE 'Salary updated successfully for an employee with emp_id %.', emp_id_param;
EXCEPTION
WHEN OTHERS THEN
-- If any error occurs, rollback the transaction
ROLLBACK;
RAISE EXCEPTION 'Error occurred: %', SQLERRM;
END;
$$;
Return cursor from prcedure
There are two kinds of CURSORs: WITH HOLD
outlive transactions and WITHOUT HOLD
(the default) don't. You may point a refcursor variable to a WITH HOLD
cursor in plpgsql with a bit of dynamic SQL. Here's a modified version of your procedure doing that:
CREATE OR REPLACE PROCEDURE public.build_and_populate(INOUT cresults refcursor)
LANGUAGE plpgsql
AS $procedure$
BEGIN
DROP TABLE IF EXISTS procsampledata;
CREATE TABLE procsampledata as select x,1 as c2,2 as c3, md5(random()::text) from generate_series(1,10) x;
COMMIT;
EXECUTE 'DECLARE ' || quote_ident(cresults::text) || ' CURSOR WITH HOLD FOR SELECT * FROM procsampledata';
END;
$procedure$
postgres=# call BUILD_AND_POPULATE('res');
cresults
----------
res
(1 row)
postgres=# fetch all from res;
x | c2 | c3 | md5
----+----+----+----------------------------------
1 | 1 | 2 | 11a3d2e637332a25118c2f4d5dac49c0
2 | 1 | 2 | 4c5b9cb5ec79479a9daa9ae7a131a078
3 | 1 | 2 | 320b49912e94de90c3370836706c5494
4 | 1 | 2 | a9ccf45dbbbbec19d18b69cf4a0fc26b
5 | 1 | 2 | 1a52801f1761cb0e357a4468803473e1
6 | 1 | 2 | 96c395dad11ee19526bc08b5ad114905
7 | 1 | 2 | 7d0da9a0cf2520ee871185677d7062cf
8 | 1 | 2 | 65ecf1e68806a0b9133fa0e47056574e
9 | 1 | 2 | bae5c688e1ef6b9c389b37ecc7a18342
10 | 1 | 2 | ed07142f3b51a282c05370890a8af468
(10 rows)
postgres=# close res;
CLOSE CURSOR