4

我有以下代码块:

unless User.exist?(...)
  begin
    user = User.new(...)
    # Set more attributes of user
    user.save!
  rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e
    # Check if that user was created in the meantime
    user = User.exists?(...)
    raise e if user.nil?
  end
end

原因是,你可能猜到了,多个进程可能同时调用这个方法来创建用户(如果它不存在的话),所以当第一个进入块并开始初始化一个新用户时,设置属性并最终调用 save!,用户可能已经创建。在这种情况下,我想再次检查用户是否存在,并且仅在仍然不存在时引发异常(=如果同时没有其他进程创建它)。

问题是,保存时会定期引发 ActiveRecord::RecordInvalid 异常!并没有从救援区救出。有任何想法吗?

编辑:

好吧,这很奇怪。我肯定错过了什么。我根据 Simone 的提示重构了代码,如下所示:

unless User.find_by_email(...).present?
  # Here we know the user does not exist yet
  user = User.new(...)
  # Set more attributes of user
  unless user.save
    # User could not be saved for some reason, maybe created by another request?
    raise StandardError, "Could not create user for order #{self.id}." unless User.exists?(:email => ...)
  end
end

现在我得到了以下异常:

ActiveRecord::RecordNotUnique: Mysql::DupEntry: Duplicate entry 'foo@bar.com' for key 'index_users_on_email': INSERT INTO `users` ...

扔在写着“除非user.save”的那一行。这个怎么可能?Rails 认为可以创建用户,因为电子邮件是唯一的,但是 Mysql 唯一索引阻止了插入?那可能性有多大?又该如何避免呢?

4

2 回答 2

3

Rails 验证无法检测数据库中的竞争条件;我们使用的解决方案是还添加数据库约束。

这是我们关于此链接的简短页面:Rails ActiveRecord 验证:validates_uniqueness_of races

于 2011-04-28T03:14:26.267 回答
3

在这种情况下,您可能希望使用迁移在用户表键上创建唯一索引,以便数据库将引发错误。

另外,不要忘记validates_uniqueness_of在您的用户模型中添加验证。

验证并不总是能防止重复数据(在同一毫秒写入两个并发请求的可能性非常小)。如果您将validates_uniqueness_of与索引结合使用,则不需要所有代码。

unless User.exist?(...)
  begin
    user = User.new(...)
    # Set more attributes of user
    user.save!
  rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e
    # Check if that user was created in the meantime
    user = User.exists?(...)
    raise e if user.nil?
  end
end

变成

user = User.new(...)
# Set more attributes of user
if user.save
  # saved
else
  # user.errors will return
  # the list of errors
end
于 2011-01-06T17:03:49.837 回答