如何在PostgreSQL存储过程中使查询成为原子?

use*_*648 3 postgresql concurrency atomic plpgsql

我认为如果一个进程从唯一的用户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;  
Run Code Online (Sandbox Code Playgroud)

gwa*_*igh 8

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

UPDATE users SET balance = balance + $2 WHERE userid = $1;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Run Code Online (Sandbox Code Playgroud)