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