Ruby ActiveRecord 如何在子记录回调更新父记录时避免数据库延迟/锁定

问题描述 投票:0回答:1

我有一个运行任务的 Sidekiq 项目,该任务创建多个子记录并更新该子对象所属的父记录。数据库是Postgres。

架构如下所示。创建子记录时,有一个 before_create 方法会更新父记录中的标志。父级有一个 before_save 方法来更新时间戳。

# t.boolean "is_updated", default: false
#  id                          :integer          not null, primary key
#t.datetime "updated_at"
class Parent < ActiveRecord::Base
  has_many :child

  def update_flag
   self.is_updated = true
  end

  before_save : set_updated_at

  def set_updated_at
    self.updated_at = Time.current 
  end
end


class ChildRecord < ActiveRecord::Base
  belongs_to :parent
  before_create :update_parent_flag

  def update_parent_flag
    if self.parent.try(:update_flag)
      self.parent.save!
    end
  end
end

当 Sidekiq 作业仅创建一条子记录时,不会出现错误。然而,当作业尝试创建一大批子记录(在一个示例中为 35 条)时,作业会长时间处于繁忙状态。我们可以看到 Postgres 连接正在等待父级更新中的锁定。

更新 Sidekiq 作业在单个事务中更新或创建一批子记录。

def perform(params, options = {})
  children = params[:children] 
  #New transaction
  ActiveRecord::Base.transaction do
    children.each do |child|
      return_code, error = create_or_update_child_record(child)
      if return_code != :created
        Rails.logger.info error
      return
     end
  end
 end

以下是数据库中的阻塞语句。

UPDATE "Parent" SET "updated_at" = $1 WHERE "Parent"."id" = $2

创建多个子记录时如何避免这种锁定?有更好的设计吗?

ruby-on-rails rails-activerecord sidekiq
1个回答
0
投票

无论如何,一一插入每个子节点都是低效的。您需要一种批量插入子对象的方法。这种批量插入方法只会在最后更新他们的父母一次。

相比 Rails 的 insert_all,我更喜欢 activerecord-import,主要是因为它可以进行模型验证。

# Make the Child models, but do not insert them.
children = [Child.new(...), Child.new(...), ...]
# Validate and insert all the children in bulk
Child.import! children, validate: true
# Get their Parents
parents = children.map(&:parent).uniq
# Update their Parents
parents.each { |parent|
  parent.update_flag
  parent.save!
}
© www.soinside.com 2019 - 2024. All rights reserved.