How to retry transaction after exception in postgreSQL
-
21-04-2021 - |
문제
In different parts of my code I have to retry transactions after exceptions. But I cant figure out how to do it. Here is my test function:
CREATE OR REPLACE FUNCTION f() RETURNS VOID AS $$
DECLARE
user_cur CURSOR FOR SELECT * FROM "user" WHERE id < 50 limit 10;
row RECORD;
counter INTEGER DEFAULT 0;
dummy INTEGER DEFAULT 0;
BEGIN
RAISE INFO 'Start... ';
OPEN user_cur;
LOOP
FETCH user_cur INTO row;
EXIT WHEN row IS NULL;
BEGIN
UPDATE "user" SET dummy = 'dummy' WHERE id = row.id;
counter := counter + 1;
dummy := 10 / (5 % counter);
RAISE NOTICE 'dummy % , user_id %', (5 % counter), row.id;
EXCEPTION WHEN division_by_zero THEN
--What should I do here to retry transaction?
END;
END LOOP;
RAISE INFO 'Finished.';
RETURN;
END;
$$ LANGUAGE plpgsql;
해결책 2
Thanks to the above comments I've found the solution:
CREATE OR REPLACE FUNCTION f() RETURNS VOID AS $$
DECLARE
row RECORD;
counter INTEGER DEFAULT 0;
dummy INTEGER DEFAULT 0;
BEGIN
-- Clear user rating
RAISE INFO 'Start... ';
FOR row IN SELECT * FROM user_prop WHERE id < 50 limit 10 LOOP
LOOP
BEGIN
UPDATE user_prop SET some_field = 'whatever' WHERE id = row.id;
counter := counter + 1;
dummy := 10 / (5 % counter);
-- exit nested loop if no exception
EXIT;
EXCEPTION
WHEN division_by_zero THEN
-- do nothing, just repeat the loop
WHEN deadlock_detected THEN
-- do nothing, just repeat the loop
END;
END LOOP;
END LOOP;
RAISE INFO 'Finished.';
RETURN;
END;
$$ LANGUAGE plpgsql;
다른 팁
For what you are trying to do, a LOOP inside the LOOP would be a proper solution. No need for expensive exception handling.
Test setup:
CREATE TEMP TABLE usr (id int, dummy text);
INSERT INTO usr VALUES
(1,'foo')
,(2,'bar')
,(3,'baz')
,(4,'blarg');
Function:
CREATE OR REPLACE FUNCTION x.foo()
RETURNS VOID AS
$BODY$
DECLARE
_r record;
_dummy integer := 0;
_ct integer := 0;
_5mod_ct integer;
BEGIN
RAISE INFO 'Start... ';
FOR _r IN
SELECT * FROM usr WHERE id < 50 LIMIT 10
LOOP
LOOP
UPDATE usr SET dummy = 'foo' WHERE id = _r.id;
_ct := _ct + 1;
_5mod_ct := 5 % _ct;
EXIT WHEN _5mod_ct > 0; -- make sure this will be TRUE eventually!
RAISE INFO '_5mod_ct = 0; repeating UPDATE!';
END LOOP;
_dummy := 10 / _5mod_ct;
RAISE NOTICE '_5mod_ct: %, _dummy: %, user.id: %',
_5mod_ct, _dummy, _r.id;
END LOOP;
RAISE INFO 'Finished.';
END;
$BODY$ LANGUAGE plpgsql;
Call:
SELECT foo()
Output:
INFO: Start...
INFO: _5mod_ct = 0; repeating UPDATE!
NOTICE: _5mod_ct: 1, _dummy: 10, user.id: 1
NOTICE: _5mod_ct: 2, _dummy: 5, user.id: 2
NOTICE: _5mod_ct: 1, _dummy: 10, user.id: 3
INFO: _5mod_ct = 0; repeating UPDATE!
NOTICE: _5mod_ct: 5, _dummy: 2, user.id: 4
INFO: Finished.
Your code example displays a number of problems:
Don't use reserved words as identifiers.
user
is a reserved word in every SQL standard and in PostgreSQL in particular. It is bad practice to call your table "user". The double-quotes make it possible. Doesn't mean it's a good idea, though.Don't declare variables of the same name as table columns you use in the function body. That leads to naming conflicts very easily.
dummy
is a variable and a column name at the same time in your example. Just another loaded foot gun. One (arbitrary) possibility is to prefix variables with a_
like I demonstrate.A
FOR
loop like I demonstrate is much simpler than explicit cursor handling.