Rails:用Postgres创建对象时重复ActiveRecord :: RecordNotUnique?

nit*_*gen 5 ruby sql postgresql activerecord ruby-on-rails

我正在使用Rails 4应用程序,该应用程序需要创建大量对象以响应来自其他系统的事件.当我调用我的某个模型时,我在主键列上遇到非常频繁的ActiveRecord::RecordNotUnique错误(由此引起PG::UniqueViolation)create!.

我在SO上找到了其他答案,建议救出异常并致电retry:

begin
  TableName.create!(data: 'here')
rescue ActiveRecord::RecordNotUnique => e
  if e.message.include? '_pkey' # Only retry primary key violations
    log.warn "Retrying creation: #{e}"
    retry
  else
    raise
  end
end
Run Code Online (Sandbox Code Playgroud)

虽然这似乎帮助,我仍然得到ActiveRecord::RecordNotUnique误差,对于已经存在于数据库(日志条目略)顺序ID:

WARN -- Retrying creation: PG::UniqueViolation: DETAIL: Key (id)=(3067) already exists.
WARN -- Retrying creation: PG::UniqueViolation: DETAIL: Key (id)=(3068) already exists.
WARN -- Retrying creation: PG::UniqueViolation: DETAIL: Key (id)=(3069) already exists.
WARN -- Retrying creation: PG::UniqueViolation: DETAIL: Key (id)=(3070) already exists.
Run Code Online (Sandbox Code Playgroud)

它正在尝试的ID在3000-4000范围内,即使表中有超过90000条记录.

为什么ActiveRecord或PostgreSQL在按顺序尝试现有ID时浪费了这么多时间?


原始异常(简化/删除查询字符串):

{
  "exception": "ActiveRecord::RecordNotUnique",
  "message": "PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint \"table_name_pkey\"\nDETAIL:  Key (id)=(3023) already exists."
}
Run Code Online (Sandbox Code Playgroud)

nit*_*gen 17

我不确定它是如何发生的,但事实证明,表的主键的PostgreSQL序列以某种方式重置或与表不同步:

SELECT nextval('table_name_id_seq');
-- 3456

SELECT max(id) FROM table_name;
-- 95123
Run Code Online (Sandbox Code Playgroud)

我必须在表的最后一个ID重新启动主键序列:

ALTER SEQUENCE table_name_id_seq RESTART 95124;
Run Code Online (Sandbox Code Playgroud)

更新:这是一个Rake任务,用于在PostgreSQL项目的Rails 4上重置大多数模型的ID序列:

desc 'Resets Postgres auto-increment ID column sequences to fix duplicate ID errors'
task :reset_sequences => :environment do
  Rails.application.eager_load!

  ActiveRecord::Base.descendants.each do |model|
    unless model.attribute_names.include?('id')
      Rails.logger.debug "Not resetting #{model}, which lacks an ID column"
      next
    end

    begin
      max_id = model.maximum(:id).to_i + 1
      result = ActiveRecord::Base.connection.execute(
        "ALTER SEQUENCE #{model.table_name}_id_seq RESTART #{max_id};"
      )
      Rails.logger.info "Reset #{model} sequence to #{max_id}"
    rescue => e
      Rails.logger.error "Error resetting #{model} sequence: #{e.class.name}/#{e.message}"
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

以下参考证明是有用的:


adc*_*adc 7

您还可以使用rails控制台重置表'table_name'的序列

> ActiveRecord::Base.connection.reset_pk_sequence!('table_name')
Run Code Online (Sandbox Code Playgroud)

(在导轨3.2,导轨5.0.1中测试)