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

时间:2013-02-18 01:28:17

标签: 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;  

1 个答案:

答案 0 :(得分:8)

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

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

更一般地说,您希望让事务系统处理原子性和数据一致性。在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;