更新比赛条件Postgres(读取已提交)

时间:2014-09-28 03:10:44

标签: sql postgresql concurrency race-condition

我试图编写一个更新"声明"仅当用户没有打开两个以上的有效声明时,表才会处于活动状态。因此,对于数据完整性非常重要的是,用户从不在任何给定时间都有两个以上的有效声明。

我在并发环境中运行此查询,因此两个进程可能同时执行此查询。我也在默认的Read Committed隔离级别下运行它。

我想知道,由于subselect和update子句之间存在竞争条件,我是否可能会在某个时刻打开两个以上的活动声明。

同样,对于此查询,性能几乎与数据完整性一样重要。

update claim
set is_active = '1'
where claim.id = %s
and (2 > (select count(*)
          from claim as active_claim
          where active_claim.user_id = %s
          and active_claim.is_active = '1'))

1 个答案:

答案 0 :(得分:10)

是的,这绝对有可能导致两个以上的有效声明,因为并发交易无法看到彼此的声音。更改,因此两个或多个并发执行都会看到2个声明,并且都会继续更新其目标声明以使其处于活动状态。

参见相关内容:Do database transactions prevent race conditions

表锁

最简单的选择就是:

BEGIN;
LOCK TABLE claim IN EXCLUSIVE MODE;
UPDATE ...
COMMIT;

......但这是一个非常重量级的解决方案。

用户对象上的行级锁定

假设您拥有声明所有者的表user,则应改为:

SELECT 1 FROM user WHERE user_id = whatever FOR UPDATE
在运行UPDATE之前,在同一笔交易中

。这样,您将对用户持有独占的行锁定,而其他SELECT ... FOR UPDATE语句将阻止您的锁定。此锁定还会阻止UPDATE的{​​{1}}和删除user;如果没有SELECTFOR UPDATE子句,它将阻止普通FOR SHARE用户。

请参阅explicit locking in the PostgreSQL manual

SERIALIZABLE隔离

另一种方法是使用SERIALIZABLE隔离; PostgreSQL 9.2及更新版本具有事务依赖性检测,在上面给出的示例中,除了一个冲突事务之外的所有事务都会因序列化失败而中止。因此,您的应用必须记住它在启动事务时尝试执行的操作,并能够捕获错误,检测到序列化失败,并在序列化失败后重新尝试。

请参阅transaction isolation in the PostgreSQL manual

咨询锁

有时没有好的候选对象可以进行行锁定,并且由于某种原因或其他可序列化的隔离无法解决问题或因其他原因而无法使用。但情况并非如此,这只是一般信息。

在这种情况下,您可以使用PostgreSQL的建议锁来锁定任意数值;在这种情况下,您可以pg_advisory_xact_lock(active_claim.user_id)为例。显式锁定章节有更多信息。