【问题标题】:Update where race conditions Postgres (read committed)更新竞争条件 Postgres 的位置(读取已提交)
【发布时间】:2014-09-28 03:10:44
【问题描述】:

我正在尝试编写一个查询,仅当用户尚未打开两个以上的活动声明时,才将“声明”表中的一行更新为活动状态。因此,对于数据完整性而言,用户从不在任何给定时间打开的有效声明不超过两个。

我在并发环境中运行此查询,因此两个进程可能同时执行此查询。我也在默认的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'))

【问题讨论】:

  • 很好地考虑了并发性,而不是仅仅假设“事务”会神奇地解决并发问题;它已经让你领先于 99% 的人。 (不过,将来也请在您的问题中包含您的 PostgreSQL 版本)。
  • 我使用的是 9.3 作为参考。

标签: sql postgresql concurrency race-condition


【解决方案1】:

是的,这绝对有可能导致两个以上的活动声明,因为并发事务无法看到彼此的更改,因此两个或多个并发执行都会看到 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 语句将阻塞您的锁。此锁还将阻止UPDATEs 并删除user;如果没有 FOR UPDATEFOR SHARE 子句,它将不会阻止用户的普通 SELECTs。

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)。显式锁定章节有更多信息。

【讨论】:

  • 哇,这是一些非常可靠的信息。对于我的用例,用户表上的行级锁定似乎是最合适的解决方案,防止同一用户将冲突信息写入他们的记录。再次感谢,这真的解决了问题。
  • SERIALIZABLE 不完美的情况是什么?
  • @Tallboy DDL 和系统目录操作是一回事。用户手册中给出的示例另作他用。
猜你喜欢
  • 1970-01-01
  • 2020-08-30
  • 2021-04-01
  • 2016-07-22
  • 2015-12-20
  • 2015-03-19
  • 2012-07-12
  • 2017-03-20
  • 2017-03-31
相关资源
最近更新 更多