并发 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;

这将序列化影响相同行的所有事务。