2012-05-17 55 views
15

我试图强制在我的表字段之一的值的唯一性。更改表格不是一个选项。我需要使用ActiveRecord有条件地插入一行到表中,但我关心同步。Rails的竞争条件first_or_create

是否first_or_create在Rails ActiveRecord中预防竞争状态?

这是从GitHub的first_or_create的源代码:

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

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

+2

AR充满了这样的竞争条件。 – dbenhur

+0

请参阅(SO dup)[我如何避免Rails应用程序中的竞态条件?](http://stackoverflow.com/questions/3037029/how-do-i-avoid-a-race-condition-in-my -rails-app)和Rails Cookbook [避免使用乐观锁定的竞态条件](http://underpop.free.fr/r/ruby-on-rails/cookbook/I_0596527314_CHP_3_SECT_19.html) – dbenhur

+0

@dbenhur - 我无法使用乐观锁定,因为它涉及到向表中添加一个字段。我的一个条件是我不能添加一个字段,所以它不是重复的。 –

回答

5

是的,这是可能的。

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

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

+0

我无法使用乐观锁定,因为修改表格不是一个选项。悲观锁定的问题是,我无法通过Rails指定我需要的锁定隔离级别,而无需插入InnoDB特定的语法。尽管可能存在重复的行,但我最终只是使用'first_or_create'。 虽然我认为我可以在ActiveRecord中使用验证助手来确保唯一性。 –

+2

他们遭受相同的竞争条件 –

17

钢轨4 documentation for find_or_create_by提供了可以用于这种情况下是有用的尖端:

请注意这种方法并不原子,它首先运行SELECT,和如果有任何结果的INSERT是尝试。如果还有其他线程或进程,则两个调用之间存在争用条件,并且可能会出现两个相似的记录。

不管这是一个问题或不依赖于应用程序的逻辑,但在该行有一个唯一的约束的异常可以被升高的特定情况下,在执行一次:

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

类似错误捕捉对于Rails 3可能会有用。(不知道Rails 3中是否会出现相同的ActiveRecord::RecordNotUnique错误,因此您的实现可能需要不同。)

+0

只是为了记录:Rails 3抛出一个'ActiveRecord :: StatementInvalid'(什么不是很具体)。 – spickermann

+0

无赖。所以你仍然可能做一个'救援ActiveRecord :: StatementInvalid'为了重试,即使它没有像Rails 4那样读取? –

+0

你可以。但问题是'StatementInvalid'也包含无效的SQL,在非空列中包含NULL,依此类推。你可能会重试那些永远不会工作的东西。也许你想使用像https://github.com/nfedyashev/retryable#readme这样的东西来避免陷入循环。 – spickermann