【问题标题】:PostgreSQL and ActiveRecord subselect for race conditionPostgreSQL 和 ActiveRecord 子选择竞争条件
【发布时间】:2015-09-04 13:29:31
【问题描述】:

我在使用 PostgreSQL 的 ActiveRecord 中遇到了争用情况,我正在读取一个值,然后将其递增并插入一条新记录:

num = Foo.where(bar_id: 42).maximum(:number)
Foo.create!({
  bar_id: 42,
  number: num + 1
}) 

在规模上,多个线程将同时读取然后写入 number 的相同值。将其包装在事务中并不能修复竞争条件,因为 SELECT 不会锁定表。我不能使用自动增量,因为number 不是唯一的,它只有在给定某个bar_id 时才是唯一的。我看到了 3 个可能的修复:

  • 显式使用 postgres 锁(行级锁?)

  • 使用唯一约束并在失败时重试(糟糕!)

  • 覆盖保存以使用子选择,即 I.E.

    INSERT INTO foo (bar_id, number) VALUES (42, (SELECT MAX(number) + 1 FROM foo WHERE bar_id = 42));

所有这些解决方案似乎我要重新实现ActiveRecord::Base#save! 的大部分内容有没有更简单的方法?

更新: 我以为我用Foo.lock(true).where(bar_id: 42).maximum(:number) 找到了答案,但它使用了SELECT FOR UDPATE,这在聚合查询中是不允许的

更新 2: 我们的 DBA 刚刚通知我,即使我们可以执行 INSERT INTO foo (bar_id, number) VALUES (42, (SELECT MAX(number) + 1 FROM foo WHERE bar_id = 42)); 也无法解决任何问题,因为 SELECT 在与 INSERT 不同的锁中运行

【问题讨论】:

  • 如果它是动态计算的,它会随着时间的推移而改变,并且受制于相同的竞争条件

标签: ruby-on-rails postgresql activerecord


【解决方案1】:

您的选择是:

  • SERIALIZABLE 隔离中运行。由于序列化失败,相互依赖的事务将在提交时中止。您会收到大量错误日志垃圾邮件,并且您会进行大量重试,但它会可靠地工作。

  • 定义 UNIQUE 约束并在失败时重试,如您所述。与上述相同的问题。

  • 如果有父对象,您可以在进行max 查询之前SELECT ... FOR UPDATE 父对象。在这种情况下,你会SELECT 1 FROM bar WHERE bar_id = $1 FOR UPDATE。您正在使用bar 作为所有foos 与bar_id 的锁。然后,您可以知道继续进行是安全的,只要每个执行计数器增量的查询都能可靠地执行此操作。这可以很好地工作。

    这仍然会为每个调用进行聚合查询,这(每个下一个选项)是不必要的,但至少它不会像上述选项那样向错误日志发送垃圾邮件。

  • 使用柜台。这就是我要做的。在bar 或像bar_foo_counter 这样的边表中,使用

    获取行ID
    UPDATE bar_foo_counter SET counter = counter + 1
    WHERE bar_id = $1 RETURNING counter
    

    如果您的框架无法处理RETURNING,则使用效率较低的选项:

    SELECT counter FROM bar_foo_counter
    WHERE bar_id = $1 FOR UPDATE;
    
    UPDATE bar_foo_counter SET counter = $1;
    

    然后,在同一事务中,将生成的计数器行用于number。当您提交时,该 bar_id 的计数器表行将解锁以供下一个查询使用。如果您回滚,更改将被丢弃。

我推荐计数器方法,使用专用边表作为计数器,而不是在bar 中添加一列。这更易于建模,意味着您在 bar 中创建的更新膨胀更少,这可能会减慢对 bar 的查询。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-17
    • 2014-04-02
    • 2014-02-23
    • 1970-01-01
    • 2020-07-12
    • 2013-11-18
    • 1970-01-01
    • 2017-01-23
    相关资源
    最近更新 更多