如何使用 ActiveRecord 和 Sidekiq 在 Postgres 中同时创建记录

How to create records concurrently in Postgres with ActiveRecord and Sidekiq

我有一个 Sidekiq 工作人员,它进行 API 调用,解析返回的 json 并创建 ActiveRecord 对象(产品)。由于产品属于一个品牌并且产品 json 也包含该品牌的数据,因此工作人员在实际将产品保存到数据库之前执行以下操作:

  1. 检查品牌是否存在于数据库中,通过检查其唯一性 api_id(id,品牌来自 api,在数据库中,有一个唯一索引列);
  2. 如果有 - 获取其主要 ID;
  3. 如果不是 - 创建它并获取其主 ID

我是这样实现的:

def brand_id(brand_json)
  Brand.where(api_id: brand_json[:api_id]).pluck(:id).first.presence ||
  Brand.create!(name: brand_json[:name], api_id: brand_json[:api_id]).id
end

之后,工作人员创建了 brand_id 设置为获取的 ID 的产品。

现在我想到了以下场景:

  1. 两个工作人员同时获取数据库中不存在的属于同一品牌的两个产品的数据;
  2. 工人 1 检查品牌,但没有找到;
  3. 不久之后,工人 2 检查品牌但没有找到;
  4. 工人 1 创建品牌;

现在工人 2 会怎样?我的假设 - 它试图创建品牌,但在数据库级别发生错误,因为已经有相同 api_id 的记录? (可能 ActiveRecord::RecordNotUnique 出现错误?)

此外,在 SidekiqActiveRecord 的上下文中,如何处理这种情况和此类错误?我是否应该以某种方式对品牌 table 实施 table 范围的锁定以防止此类事情发生?如果是 - 那么我将无法同时创建产品,因为在任何给定时间只有一名工人可以访问品牌 table,这是创建产品所必需的。

或者也许我应该像这样在事务中包装我的 brand_id(brand_json) 方法:

def brand_id(brand_json)
  ActiveRecord::Base.transaction do
    Brand.where(api_id: brand_json[:api_id]).pluck(:id).first.presence ||
    Brand.create!(name: brand_json[:name], api_id: brand_json[:api_id]).id
  end
end

请指教

  1. 将唯一索引约束(可能以多列索引的形式)放入数据库中。
  2. 只需尝试创建对象。数据库会阻止你制作多个。
  3. 只有成功创建初始对象(未发生异常)的线程才能进行额外处理。