我正试图在我的一个表字段中强制执行值的唯一性。更改表格不是一种选择。我需要使用ActiveRecord有条件地在表中插入一行,但我担心同步。
Rails ActiveRecord中的first_or_create
会阻止竞争条件吗?
这是来自GitHub的first_or_create
的源代码:
def first_or_create(attributes = nil, options = {}, &block)
first || create(attributes, options, &block)
end
由于多个进程的同步问题,重复条目是否可能导致数据库?
答案 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)
是的,这是可能的。
您可以显着降低与optimistic或pessimistic 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