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

rec*_*gle 11 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'))
Run Code Online (Sandbox Code Playgroud)

Cra*_*ger 11

是的,这绝对有可能导致两个以上的活动声明,因为并发事务无法看到彼此的更改,因此两个或更多并发执行都会看到2个声明,并且都会继续更新其目标声明以使它们成为可能活性.

请参阅相关:数据库事务是否阻止竞争条件.

表锁

最简单的选择是:

BEGIN;
LOCK TABLE claim IN EXCLUSIVE MODE;
UPDATE ...
COMMIT;
Run Code Online (Sandbox Code Playgroud)

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

用户对象上的行级锁定

假设您有一张user索赔所有者的表格,您应该:

SELECT 1 FROM user WHERE user_id = whatever FOR UPDATE
Run Code Online (Sandbox Code Playgroud)

在同一笔交易中,在运行之前UPDATE.这样你就可以对用户进行独占的行锁定,其他SELECT ... FOR UPDATE语句将阻止你的锁定.这个锁也会阻止UPDATEs和删除user; 没有or 子句,它不会阻止SELECT用户的普通s .FOR UPDATEFOR SHARE

请参阅PostgreSQL手册中的显式锁定.

SERIALIZABLE 隔离

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

请参阅PostgreSQL手册中的事务隔离.

咨询锁

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

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