提问人:Shankar 提问时间:11/11/2023 最后编辑:Shankar 更新时间:11/15/2023 访问量:42
Ruby ActiveRecord 如何在子记录回调更新父记录时避免数据库延迟/锁定
Ruby ActiveRecord how to avoid Database delay/lock when child record callback is updating parent record
问:
我有一个 Sidekiq 项目,正在运行一个任务,创建多个子记录并更新此子对象所属的父记录。数据库是 Postgres。
架构如下。创建 Child 记录时,有一个 before_create 方法可以更新 Parent 中的标志。父级具有更新时间戳的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 连接正在等待 Parent 更新中的锁定。
更新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
创建多个子记录时如何避免此锁定?有没有更好的设计?
答:
2赞
Schwern
11/11/2023
#1
无论如何,一个接一个地插入每个子项都是低效的。您需要一种批量插入 Child 对象的方法。这种批量插入方法在最后只会更新其父项一次。
我更喜欢 activerecord-import 而不是 Rails 的insert_all,主要是因为它可以进行模型验证。
# 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 Parent's ids
parent_ids = children.map(&:parent_id).uniq
# Update their Parents
Parent
.where(id: parent_ids)
.update_all(
is_updated: true,
# update_all does not update updated_at
updated_at: Time. current
)
这将为子项执行一次插入,为其父项执行一次更新。
注意:考虑是否需要 is_updated 标志。您可以仅依靠updated_at时间戳吗?
评论
update_flag
似乎尝试创建一个 在创建子项之前,它会调用,然后调用 ,它试图创建一个 ....我很惊讶你没有最终进入.“有没有更好的设计?” stackoverflow.com/questions/6736265/......Child
update_parent_flag
update_flag
Child
SystemStackError
update_flag
is_updated
Parent
params[:children]