【问题标题】:find_or_create race conditionsfind_or_create 竞争条件
【发布时间】:2011-05-06 21:48:05
【问题描述】:

我正在尝试使用 ActiveRecord 的 find_or_create_by_*column*,但我收到来自 Postgres 的错误,让我知道它有时无法找到模型,并且无论如何都会尝试插入一个模型。保持这个表的唯一性非常重要,所以我在它的迁移中添加了一个:unique => true 属性,这样 Postgres 就会知道我是认真的。

然后,失败:

ActiveRecord::StatementInvalid: PGError: ERROR: duplicate key value violates unique constraint "index_marketo_leads_on_person_id" DETAIL: Key (person_id)=(9968932) already exists. : INSERT INTO "marketo_leads" ("mkt_person_id", "synced_at", "person_updated_at", "person_id") VALUES(NULL, NULL, '2011-05-06 12:57:02.447018', 9968932) RETURNING "id"

我有这样的模型:

class User < AR::Base
  has_one :marketo_lead

  before_save :update_marketo_lead

  def update_marketo_lead
    if marketo_lead
      if (User.marketo_columns & self.changes.keys).any?  
        marketo_lead.touch(:person_updated_at) 
      end
    elsif self.id
      marketo_lead = MarketoLead.find_or_create_by_person_id(:person_updated_at => Time.now, :person_id => self.id) 
    end
  end
end

class MarketoLead
  belongs_to :user, :foreign_key => 'person_id'
end

第二种模型用于将我们的用户帐户链接到 Marketo 电子邮件服务器,并记录用户的某些字段上次修改的时间,以便我们可以在批处理后台任务中推送更改的记录。

除了某种我无法想象的竞争条件之外,我想不出任何原因导致此回调 update_marketo_lead 失败。

(请忽略 'user' 与 'person' 共享主键的可怕性) (使用 Rails 2.3.11,Postgres 9.0.3)

【问题讨论】:

    标签: ruby-on-rails activerecord callback


    【解决方案1】:

    很有可能在执行 find_or_create 时,找不到匹配的 person_id,因此使用了创建逻辑,但是在 find_or_create 和实际 user.save 之间,另一个请求可能成功完成了保存事务,此时您的数据库约束导致此异常。

    我建议捕获 StatementInvalid 异常并重试保存(最多有限次...

    begin
       user.save!
    rescue ActiveRecord::StatementInvalid => error
      @save_retry_count =  (@save_retry_count || 5)
      retry if( (@save_retry_count -= 1) > 0 )
      raise error
    end
    

    请注意,无论您尝试保存用户,都应该执行此操作。所有回调和验证都发生在 save!交易

    附:我假设您的 rails 版本支持事务 :) 在 Rails 3 中,无需包装保存!在事务中,因为它已经在内部使用了一个

    【讨论】:

    • 虽然为触发 user.save 的事情编辑我的应用程序并不实际,但我非常感谢有关拯救 StatementInvalid 错误和添加有限重试循环的好建议。我在find_or_create 调用周围添加了这样的异常处理。干杯!
    • 在我的应用程序中,models.ave 是从控制器触发的,因此将其包装在 begin - 救援中相当容易。事实上,我在需要这个的模型中添加了一个名为 save_with_retries(retry_count=5) 的方法
    • P.S 我删除了 user.save 周围的事务,因为 ActiveRecord(根据事务文档)已经包装了保存,保存!并销毁内部交易。
    【解决方案2】:

    我在一个辅助工作中遇到了这个问题,该工作重试并反复获取错误并最终自行清除。我不相信这是另一个请求的竞争条件,否则它会非常罕见,只会发生一次或两次,但不会像我看到的那样连续发生 11 次。我找到的最好的解释是在博客文章here 上。要点是 postgres 保留一个内部存储的值,用于增加以某种方式搞砸的主键。这对我来说是正确的,因为我正在设置主键,而不仅仅是使用递增的值,所以也许这就是它的出现方式。上面链接中 cmets 的解决方案似乎是调用 ActiveRecord::Base.connection.reset_pk_sequence!(table_name)

    我无法验证这一点,因为我无法重现该问题,但我尝试的修复,从 Vladimir 的上述修复修改为:

    begin
       user.save!
    rescue ActiveRecord::StatementInvalid => error
       @save_retry_count =  (@save_retry_count || 1)
       ActiveRecord::Base.connection.reset_pk_sequence!(:user)
       retry if( (@save_retry_count -= 1) >= 0 )
       raise error
    end
    

    所以如果这在第一次尝试时没有解决它,我会看到一个错误提示

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-23
      • 2018-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多