【问题标题】:Race conditions in Rails first_or_createRails first_or_create 中的竞争条件
【发布时间】:2012-05-17 18:42:46
【问题描述】:

我正在尝试在我的一个表字段中强制执行值的唯一性。更换桌子不是一种选择。我需要使用 ActiveRecord 有条件地向表中插入一行,但我担心同步。

Rails ActiveRecord 中的first_or_create 是否可以防止竞争条件?

这是来自 GitHub 的 first_or_create 的源代码:

def first_or_create(attributes = nil, options = {}, &block)
  first || create(attributes, options, &block)
end

是否有可能由于多进程同步问题导致数据库中出现重复条目​​?

【问题讨论】:

  • AR 充满了这样的竞争条件。
  • @dbenhur - 我不能使用乐观锁定,因为它涉及向表中添加一个字段。我的条件之一是我不能添加一个字段,所以它不是重复的。
  • 如果你不能使用乐观锁,你可以尝试显式的悲观锁(虽然这会损害性能),或者你可以在数据库中添加约束,并捕获约束违规,并使用适当的语义重试.

标签: ruby-on-rails ruby-on-rails-3 activerecord


【解决方案1】:

Rails 4 documentation for find_or_create_by 提供了一个可能对这种情况有用的提示:

请注意此方法不是原子的,它首先运行 SELECT,如果没有结果,则尝试 INSERT。如果有其他线程或进程在两个调用之间存在竞争条件,那么您最终可能会得到两条相似的记录。

这是否有问题取决于应用程序的逻辑,但在行具有 UNIQUE 约束的特定情况下,可能会引发异常,只需重试:

begin
  CreditAccount.find_or_create_by(user_id: user.id)
rescue ActiveRecord::RecordNotUnique
  retry
end

类似的错误捕获可能对 Rails 3 有用。(不确定 Rails 3 中是否抛出相同的 ActiveRecord::RecordNotUnique 错误,因此您的实现可能需要不同。)

【讨论】:

  • 仅作记录:Rails 3 抛出一个ActiveRecord::StatementInvalid(不是很具体)。
  • 无赖。所以你仍然可以通过rescue ActiveRecord::StatementInvalid 来重试,即使它的读取不如 Rails 4 好?
  • 你可以。但问题是StatementInvalid 还包括无效的SQL,非空列中的NULL 等等。您可能会重试永远不会起作用的事情。也许您想使用类似github.com/nfedyashev/retryable#readme 的东西来避免陷入循环。
  • 更多的是一个问题而不是一个论点...... retry 只会运行一次,不是吗?因此,如果它是糟糕的 SQL 或其他什么,它会在第二次运行时硬失败。
  • 不,retry 会在之前的块中出现错误时重试块。如果你想避免陷入循环,你需要计算你重试的次数,如果tries > max_tries,你不能重试(但引发)。查看retryable gem。
【解决方案2】:

是的,这是可能的。

您可以显着降低与optimisticpessimistic locking 发生冲突的可能性。当然,乐观锁定需要向表中添加一个字段,而悲观锁定的扩展性较差——另外,它取决于您的数据存储的能力。

我不确定您是否需要额外的保护,但它是可用的。

【讨论】:

  • 我不能使用乐观锁定,因为修改表不是一种选择。悲观锁定的问题在于,如果不插入特定于 InnoDB 的语法,我就无法通过 Rails 指定我需要的锁定隔离级别。尽管可能存在重复行,但我最终还是使用了first_or_create。虽然我认为我可以使用 ActiveRecord 中的验证助手来确保唯一性。
  • 他们遭受相同的竞争条件
【解决方案3】:

我不认为 first_or_create 是原子的。从控制台我看到一个选择操作,然后创建。 所以需要锁定。 如果您使用悲观锁,则不需要额外的 col。 使用advisory_lock。

SomeModel.with_advisory_lock("get_or_create_#{some_key}") do
    SomeModel.where(external_id: external_id).first_or_create
end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-07
    • 2011-07-17
    • 2013-02-27
    • 2021-04-16
    相关资源
    最近更新 更多