Rails技术问题:after_create中排除新创建记录,保证单活跃Registry
解决同一客户仅一个活跃Registry的竞态问题
1. 先加数据库唯一约束(兜底必备)
这是最核心的一步,从数据库层面杜绝多个活跃记录的可能,不管代码逻辑怎么写,数据库都会帮你拦截:
# 生成迁移文件 rails generate migration AddUniqueActiveIndexToRegistries
编辑迁移文件:
class AddUniqueActiveIndexToRegistries < ActiveRecord::Migration[7.0] def change # 仅当active=true时,customer_id必须唯一 add_index :registries, [:customer_id, :active], unique: true, where: "active = true" end end
执行迁移:rails db:migrate
2. 替换回调为原子SQL更新(解决竞态)
别再遍历修改每条记录了,用单条SQL批量更新,原子性更强,还能避免并发问题:
class Registry < ApplicationRecord belongs_to :customer after_create :deactivate_other_active_registries, if: :active? private def deactivate_other_active_registries # 用update_all执行原子更新,直接操作数据库,不会有中间状态 Registry.where(customer_id: customer_id) .where.not(id: id) .where(active: true) .update_all(active: false) end end
如果担心极端并发,还可以加上行锁,确保同一时间只有一个请求能修改该客户的Registry:
def deactivate_other_active_registries ActiveRecord::Base.transaction do # 锁定该客户的所有Registry记录,阻塞其他并发请求 customer.registries.lock Registry.where(customer_id: customer_id) .where.not(id: id) .where(active: true) .update_all(active: false) end end
3. 用服务对象封装逻辑(更可控)
把创建Registry的逻辑从模型回调里抽出来,改成服务对象,逻辑更清晰,也更容易排查问题:
class RegistryCreator def self.create_for_customer(customer, params) ActiveRecord::Base.transaction do # 先锁再操作,避免并发 customer.registries.lock # 创建新的活跃Registry new_registry = customer.registries.create!(params.merge(active: true)) # 批量禁用旧的活跃记录 Registry.where(customer_id: customer.id) .where.not(id: new_registry.id) .where(active: true) .update_all(active: false) new_registry end end end
使用时直接调用:RegistryCreator.create_for_customer(current_customer, registry_params)
为啥之前的回调不好使?
after_create执行时,新记录已经写入DB,但并发请求可能同时读取到“没有其他活跃记录”的脏数据,导致两条记录都设为活跃。- 换成
after_save加new_record?判断本质没区别,还是没解决并发下的读一致性问题。
内容的提问来源于stack exchange,提问作者Corey
相关产品推荐
相关产品推荐

