问题
I have this situation with thread process. Have table like example :
id | type | external_key | balance | amount | date
1 TOPUP MT1 10 2019-01-1 13:01:00.500110
2 USAGE MT1 -1 2019-01-1 13:01:01.300100
3 TOPUP MT3 5 2019-01-1 13:01:02.400300
every new data on each row i run this trigger to update field balance to store current balance every row :
create or replace function function_update_balance() returns trigger
language plpgsql
as $$
BEGIN
IF new.type = 'CBA_ADJ' THEN
update example set balance=(SELECT SUM(case when amount >= 0 then amount when amount <= 0 then amount end) FROM example WHERE id=id and type=new.type and external_key =new.external_key ) WHERE id=new.id and date<= new.date;
END IF;
return null;
END;
$$
;
if the row insert not to fast (not like thread), the balance have the correct balance like this:
id | type | external_key | balance | amount | date
1 TOPUP MT1 10 10 2019-01-1 13:01:00.500110
2 USAGE MT1 9 -1 2019-01-1 13:01:01.300100
3 TOPUP MT3 5 5 2019-01-1 13:01:02.400300
but when the data insert on thread (just different on per , milisecond the balance not have correct result like this:
id | type | external_key | balance | amount | date
1 TOPUP MT1 10 10 2019-01-1 13:01:00.500110
2 USAGE MT1 9 -1 2019-01-1 13:01:01.300100
3 TOPUP MT3 5 5 2019-01-1 13:01:02.400300
4 USAGE MT1 8 -1 2019-01-1 13:01:03.404000
5 USAGE MT1 8 -1 2019-01-1 13:01:02.405000
6 USAGE MT1 8 -1 2019-01-1 13:01:03.407000
7 USAGE MT1 8 -1 2019-01-1 13:01:03.408000
8 USAGE MT1 4 -1 2019-01-1 13:01:05.612000
that's like, the select on update have read same value before last transaction was commit, i have try with isolation like this :
create or replace function function_update_balance() returns trigger
language plpgsql
as $$
BEGIN
IF new.type = 'CBA_ADJ' THEN
start transaction isolation level repeatable read;
update example set balance=(SELECT SUM(case when amount >= 0 then amount when amount <= 0 then amount end) FROM example WHERE id=id and type=new.type and external_key =new.external_key ) WHERE id=new.id and date<= new.date;
END IF;
return null;
END;
$$
;
then give me error :
ERROR: cannot begin/end transactions in PL/pgSQL
Hint: Use a BEGIN block with an EXCEPTION clause instead.
Where: PL/pgSQL function function_update_currentbalance() line 5 at SQL statement
any clue for this issue with concurrency process ?
回答1:
That is a weird trigger function.
However, as you have noticed, concurrent inserts will cause the trigger functions to run concurrently, so that they cannot see each other's data modifications.
If you need to avoid that, you have to serialize the execution using locks.
You could
LOCK example IN SHARE ROW EXCLUSIVE MODE;
in the trigger function, but since that will prevent concurrent autovacuum runs, it is not such a hot idea.
Better would be to use a row lock:
PERFORM 1 FROM example
WHERE type = NEW.type and external_key = NEW.external_key
ORDER BY id, date
FOR UPDATE OF example;
That will serialize all transactions that affect the same rows.
来源:https://stackoverflow.com/questions/57865130/concurrency-postgresql-update