嗨,我有这段代码
class Place < ActiveRecord::Base
def self.find_or_create_by_latlon(lat, lon)
place_id = call_external_webapi
result = Place.where(:place_id => place_id).limit(1)
result = Place.create(:place_id => place_id, ... ) if result.empty? #!
result
end
end
然后我想在另一个模型或控制器中做
p = Post.new
p.place = Place.find_or_create_by_latlon(XXXXX, YYYYY) # race-condition
p.save
但 Place.find_or_create_by_latlon 如果执行的操作是 create ,则有时需要太多时间来获取数据,有时在生产中 p.place 是为零。
在执行 p.save 之前,如何强制等待响应? 谢谢你的建议
答案 0 :(得分:1)
你是对的,这是一种竞争条件,它通常可以由双击表单上的提交按钮的人触发。如果遇到错误,您可能会做的是回送。
result = Place.find_by_place_id(...) ||
Place.create(...) ||
Place.find_by_place_id(...)
有更优雅的方法可以做到这一点,但基本方法就在这里。
答案 1 :(得分:0)
我不得不处理类似的问题。在我们的后端,如果用户不存在,则从令牌创建用户。在创建用户记录之后,会发送一个缓慢的API调用来更新用户信息。
def self.find_or_create_by_facebook_id(facebook_id)
User.find_by_facebook_id(facebook_id) || User.create(facebook_id: facebook_id)
rescue ActiveRecord::RecordNotUnique => e
User.find_by_facebook_id(facebook_id)
end
def self.find_by_token(token)
facebook_id = get_facebook_id_from_token(token)
user = User.find_or_create_by_facebook_id(facebook_id)
if user.unregistered?
user.update_profile_from_facebook
user.mark_as_registered
user.save
end
return user
end
该策略的步骤是首先从create方法中删除缓慢的API调用(在我的例子中为update_profile_from_facebook)。由于操作需要很长时间,因此当您将操作作为创建调用的一部分包含在内时,显着增加了重复插入操作的可能性。
第二步是向数据库列添加唯一约束,以确保不会创建重复项。
最后一步是创建一个函数,在少数情况下捕获RecordNotUnique异常,其中重复的插入操作被发送到数据库。
这可能不是最优雅的解决方案,但它对我们有用。
答案 2 :(得分:0)
我在一个sidekick工作中点击这个,重复并重复获取错误并最终清除。我发现的最好的解释是在博客here上。要点是postgres保留了一个内部存储的值,用于递增以某种方式搞砸的主键。这对我来说是正确的,因为我正在设置主键,而不仅仅是使用递增的值,因此可能会出现这种情况。上面链接中的评论解决方案似乎是致电ActiveRecord::Base.connection.reset_pk_sequence!(table_name)
这为我解决了这个问题。
begin
result = Place.where(:place_id => place_id).limit(1)
result = Place.create(:place_id => place_id, ... ) if result.empty? #!
rescue ActiveRecord::StatementInvalid => error
@save_retry_count = (@save_retry_count || 1)
ActiveRecord::Base.connection.reset_pk_sequence!(:place)
retry if( (@save_retry_count -= 1) >= 0 )
raise error
end