并发 Postgresql 更新
Concurrency Postgresql UPDATE
我有线程进程的这种情况。有 table 例如:
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
每一行上的每个新数据我 运行 此触发器更新字段余额以存储每行的当前余额:
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;
$$
;
如果行插入速度不快(不像线程),则平衡具有正确的平衡,如下所示:
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
但是当数据在线程上插入时(只是在每毫秒上不同,余额没有像这样的正确结果:
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
就像,更新时的 select 在提交最后一个事务之前读取了相同的值,我尝试过这样的隔离:
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;
$$
;
然后给我错误:
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
关于并发过程的这个问题有什么线索吗?
这是一个奇怪的触发函数。
但是,正如您所注意到的,并发插入会导致触发器函数运行并发,因此它们无法看到彼此的数据修改。
如果你需要避免这种情况,你必须使用锁序列化执行。
你可以
LOCK example IN SHARE ROW EXCLUSIVE MODE;
在触发函数中,但由于这会阻止并发 autovacuum 运行s,所以这不是一个热门的想法。
最好使用行锁:
PERFORM 1 FROM example
WHERE type = NEW.type and external_key = NEW.external_key
ORDER BY id, date
FOR UPDATE OF example;
这将序列化影响相同行的所有事务。
我有线程进程的这种情况。有 table 例如:
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
每一行上的每个新数据我 运行 此触发器更新字段余额以存储每行的当前余额:
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;
$$
;
如果行插入速度不快(不像线程),则平衡具有正确的平衡,如下所示:
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
但是当数据在线程上插入时(只是在每毫秒上不同,余额没有像这样的正确结果:
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
就像,更新时的 select 在提交最后一个事务之前读取了相同的值,我尝试过这样的隔离:
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;
$$
;
然后给我错误:
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
关于并发过程的这个问题有什么线索吗?
这是一个奇怪的触发函数。
但是,正如您所注意到的,并发插入会导致触发器函数运行并发,因此它们无法看到彼此的数据修改。
如果你需要避免这种情况,你必须使用锁序列化执行。
你可以
LOCK example IN SHARE ROW EXCLUSIVE MODE;
在触发函数中,但由于这会阻止并发 autovacuum 运行s,所以这不是一个热门的想法。
最好使用行锁:
PERFORM 1 FROM example
WHERE type = NEW.type and external_key = NEW.external_key
ORDER BY id, date
FOR UPDATE OF example;
这将序列化影响相同行的所有事务。