5

我认为如果一个进程从唯一的用户 ID 中选择余额并尝试进行插入,那么余额将被错误地更新,但另一个进程在此之前读取了余额。我该如何解决?

CREATE OR REPLACE FUNCTION incBalance(INTEGER, BIGINT) RETURNS void AS $$   
DECLARE   
    balanceRecord record;
    newBalance bigint;  
BEGIN   
    FOR balanceRecord IN    
        SELECT balance FROM users WHERE userid = $1
    LOOP
        newBalance := balanceRecord.balance + $2;
        UPDATE users SET balance = newBalance WHERE userid = $1;   

    END LOOP; 
    RETURN;   
END;   
$$ LANGUAGE plpgsql;  
4

1 回答 1

10

对于这个特定的查询,您可以将其重写为单个 SQL 语句:

UPDATE users SET balance = balance + $2 WHERE userid = $1;

更一般地说,您希望让事务系统处理原子性和数据一致性。在 Postgres 中,存储过程总是在事务上下文中执行——如果你不是从显式事务块调用它,它会为你创建一个。

http://www.postgresql.org/docs/14/static/sql-set-transaction.html讨论了如果默认值不够严格,如何设置隔离级别。

您将需要阅读http://www.postgresql.org/docs/14/static/mvcc.html以帮助确定适合特定存储过程的级别。请注意第 13.2.2 和 13.2.3 节,它们警告说更高的隔离级别会受到序列化异常的影响,这些异常应该被捕获并且事务重试作为确保一致性的机制。

如果我有这样的过程,我会在过程的第一个 BEGIN 块的开头添加一条语句,以确保事务在足够的隔离级别上运行。如果事务中还没有做任何工作,它会在必要时提出它。如果调用上下文是一个完成工作的事务,如果封闭的事务块尚未充分提高隔离级别,则会导致错误。如果隔离级别已经高于您在此处指定的级别,它不会降低隔离级别

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
于 2013-02-18T04:08:02.637 回答