我正在尝试使用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
失败,除了某种我无法想象的竞争条件。
(请忽略'用户'与'人'共享主键的可怕性) (使用Rails 2.3.11,Postgres 9.0.3)
答案 0 :(得分:5)
很可能当执行find_or_create时,找不到匹配的person_id,因此使用了create logic,但是它可能在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
请注意,只要您尝试保存用户,就应该执行此操作。所有回调和验证都在保存中发生!交易
P.S。我假设您的rails版本支持事务:)在Rails 3中,它不需要包装保存!在事务中,因为它已经在内部使用了一个
答案 1 :(得分:0)
我在一个重复的工作中遇到这个问题,重复并重复获取错误并最终清除。我不相信它的竞争条件来自另一个请求,或者它真的很少见,只发生一次或两次但不是连续11次,就像我看到的那样。我发现的最佳解释是在博文here上。要点是postgres保留了一个内部存储的值,用于递增以某种方式搞砸的主键。这对我来说是正确的,因为我设置主键而不仅仅是使用递增的值,所以可能会出现这种情况。上述链接中的评论解决方案似乎是致电ActiveRecord::Base.connection.reset_pk_sequence!(table_name)
我无法对此进行验证,因为我无法重现这个问题,但我从上面修改弗拉基米尔修正后的尝试修复是:
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
因此,如果在第一次尝试时没有修复它,我会看到引发的错误