Rails:ActiveRecord:RecordNotUnique with first_or_create

时间:2016-05-12 09:59:46

标签: ruby-on-rails concurrency

我的模型表中有这个索引:

UNIQUE KEY `index_panel_user_offer_visits_on_offer_id_and_panel_user_id` (`offer_id`,`panel_user_id`)

这段代码:

def get_offer_visit(offer_id, panel_user_id)
  PanelUserOfferVisit.where(:offer_id => offer_id, :panel_user_id => panel_user_id).first_or_create!
end

在我们的应用程序中随机导致ActiveRecord :: RecordNotUnique异常, 问题是:

  

密钥的重复条目   'index_panel_user_offer_visits_on_offer_id_and_panel_user_id'

我在rails doc上读到first_or_create!不是一个原子方法,并且可以在高并发环境中引起这种问题,并且解决方案只是捕获异常并重试。

我尝试了不同的方法,包括Retriable gem重试一定次数来重复操作,但仍然会引发RecordNotUnique。

我甚至试图改变逻辑:

def get_offer_visit(offer_id, panel_user_id)
  begin
    offer_visit = PanelUserOfferVisit.where(:offer_id => offer_id, :panel_user_id => panel_user_id).first_or_initialize
    offer_visit.save!        
    offer_visit

  rescue ActiveRecord::RecordNotUnique
    offer_visit = PanelUserOfferVisit.where(:offer_id => offer_id, :panel_user_id => panel_user_id).first
    raise Exception.new("offer_visit not found") if offer_visit.nil?

    offer_visit
  end
end

但代码仍然引发了我所做的自定义异常,我真的不明白它是如何在第一次尝试时无法创建记录因为记录存在但是当它试图找到它找不到的记录时它再次。

我错过了什么?

2 个答案:

答案 0 :(得分:0)

如果我理解正确,你的问题是由于竞争条件造成的。我没有从Github找到确切的代码片段,但我们可以说方法find_or_create就像(编辑:这里是the code):

def first_or_create!(attributes = nil, &block)
  first || create!(attributes, &block)
end

您应尝试使用optimisticpessimistic locking

解决竞争条件

在自定义方法中实现锁定后,您应该继续捕获可能的NotUnique异常,但尝试清除缓存或使用uncached阻止之前再次执行查找。

答案 1 :(得分:0)

我遇到了类似的问题,但根本原因与竞争条件无关。

使用 first_or_create!,INSERT 会删除值中的周围空格,但 SELECT 不会。

所以如果你试试这个:

Users.where(email: 'user@mail.com ')

(注意字符串末尾的空格),生成的sql查询将是:

SELECT  "users".* FROM "users" WHERE "users"."email" = 'user@mail.com ' ORDER BY "users"."id";

这会导致找不到可能存在的记录,那么插入查询将是:

INSERT INTO "users" ("email") VALUES ('user@mail.com');

如果已存在以“user@mail.com”作为电子邮件的记录,则可能导致 ActiveRecord::RecordNotUnique 错误。

我没有关于您的问题的足够详细信息,因此它可能无关,但可能对其他人有帮助。