Rails中的竞争条件first_or_create

时间:2012-05-17 18:42:47

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

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

Rails ActiveRecord中的first_or_create会阻止竞争条件吗?

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

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

由于多个进程的同步问题,重复条目是否可能导致数据库?

3 个答案:

答案 0 :(得分:20)

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错误,因此您的实现可能需要不同。)

答案 1 :(得分:5)

是的,这是可能的。

您可以显着降低与optimisticpessimistic locking发生冲突的可能性。当然,乐观锁定需要在表格中添加一个字段,而悲观锁定也不会扩展 - 此外,它还取决于您的数据存储的功能。

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

答案 2 :(得分:0)

我不认为 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