18

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

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

这是first_or_create来自 GitHub 的源代码:

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

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

4

3 回答 3

26

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

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

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

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

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

于 2013-11-01T12:23:10.063 回答
5

是的,这是可能的。

您可以显着减少与乐观悲观锁定发生冲突的机会。当然,乐观锁定需要向表中添加一个字段,而悲观锁定的扩展性也不好——另外,这取决于您的数据存储的能力。

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

于 2012-05-17T18:51:22.907 回答
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
于 2021-05-02T11:07:30.103 回答