find_or_create ActiveRecord :: RecordNotUnique rescue retry无法正常工作

Joh*_*n S 5 ruby activerecord ruby-on-rails

使用ruby 2.1.2和rails 4.1.4.我对数据库中的电子邮件列有一个独特的约束.如果存在竞争条件,其中一个线程正在创建用户而另一个线程同时尝试创建记录而不是唯一异常.我试图通过重试来处理此异常,以便在重试时它将在find_or_create中的SELECT期间找到客户.

但是,它似乎没有起作用.它没有重试,然后重新引发异常.

这是我最初在做的事情:

retries = 10
begin
  user = User.find_or_create_by(:email => email)
rescue ActiveRecord::RecordNotUnique
  retry unless (retries-=1).zero? 
  raise   
end
Run Code Online (Sandbox Code Playgroud)

然后我想可能数据库连接正在缓存SELECT查询结果,导致它认为用户仍然不存在并继续尝试创建它.为了解决这个问题,我尝试使用Model.uncached来禁用查询缓存:

retries = 10
begin
  User.uncached do
    user = User.find_or_create_by(:email => email)
  end
rescue ActiveRecord::RecordNotUnique
  retry unless (retries-=1).zero? 
  raise   
end
Run Code Online (Sandbox Code Playgroud)

`

这也行不通.我不知道还有什么办法可以解决这个问题?我应该增加重试吗?在重试之间添加睡眠延迟?有没有更好的方法来清除查询缓存(如果这是问题?)

有任何想法吗?

谢谢!

Tor*_*rud 0

如果您使用 Postgres,那么有一个 upsert ( https://github.com/jesjos/active_record_upsert ) gem,可以在这样的场景中轻松添加“ON CONFLICT DO NOTHING/UPDATE”。您可以将您的代码替换为

User.upsert(email: email)
Run Code Online (Sandbox Code Playgroud)

如果您想在同一操作中添加或更新其他数据作为同一 upsert 调用的一部分,则应首先指定电子邮件是模型的唯一键:

class User < ActiveRecord::Base
  upsert_keys [:email]
end
Run Code Online (Sandbox Code Playgroud)

编辑:要确定您的方法的问题,请尝试发布日志的相关部分,我们可以在其中看到正在发出哪些 SQL 语句,包括事务 BEGIN/COMMIT。