我的模型表中有这个索引:
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
但代码仍然引发了我所做的自定义异常,我真的不明白它是如何在第一次尝试时无法创建记录因为记录存在但是当它试图找到它找不到的记录时它再次。
我错过了什么?
答案 0 :(得分:0)
如果我理解正确,你的问题是由于竞争条件造成的。我没有从Github找到确切的代码片段,但我们可以说方法find_or_create
就像(编辑:这里是the code):
def first_or_create!(attributes = nil, &block)
first || create!(attributes, &block)
end
您应尝试使用optimistic或pessimistic 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
错误。
我没有关于您的问题的足够详细信息,因此它可能无关,但可能对其他人有帮助。