【问题标题】:How to make queries atomic in PostgreSQL stored procedures?如何在 PostgreSQL 存储过程中使查询原子化?
【发布时间】:2013-02-02 09:45:09
【问题描述】:

我认为如果一个进程从唯一的用户 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;  

【问题讨论】:

    标签: postgresql concurrency atomic plpgsql


    【解决方案1】:

    对于这个特定的查询,您可以将其重写为一条 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;
    

    【讨论】:

    • 如果我们同时执行数千个查询,每个查询都为特定用户生成balance = balance + $2,会发生什么?难道不是在某个时候balance 会取自较早的时间,增加/减少 2 美元,失去一些中间添加/减少吗?为什么?
    • @PF4Public 使用符合ACID 的数据库的全部意义在于可以避免此类问题。 ISOLATION LEVEL READ COMMITTED 对于显示的单个 UPDATE 语句就足够了。如提供的文档链接中所述,更复杂的交易可能需要更高的级别。
    • 感谢您的回答和澄清
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-01
    相关资源
    最近更新 更多