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

时间:2015-02-20 22:35:45

标签: sql ruby-on-rails ruby postgresql activerecord

我正在使用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

虽然这似乎有所帮助,但对于已存在于数据库中的顺序ID(缩写日志条目),我仍然会收到 ActiveRecord::RecordNotUnique错误:

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.

它尝试的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."
}

2 个答案:

答案 0 :(得分:17)

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

SELECT nextval('table_name_id_seq');
-- 3456

SELECT max(id) FROM table_name;
-- 95123

我不得不重新启动表格的最后一个ID:

ALTER SEQUENCE table_name_id_seq RESTART 95124;

更新: 这里是一个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

以下参考文献证明是有用的:

答案 1 :(得分:6)

您还可以使用rails console

重置表'table_name'的序列
.list-item {
    list-style-tyle: none;
    display: inline-block;
    padding: 15px 25px;
    cursor: pointer;
    border: 1px solid red;
    transition: 0.3s all ease-in-out;
}
.list-item:hover {
    background: red;
    color: white;
}
.text-here {
    position: relative;
    top: 40px;
    transition: 0.3s all ease-in-out;
    opacity: 0;
    padding-left: 40px;
}

(在rails 3.2,rails 5.0.1中测试)