Laravel的Observer是否需要单独开启数据库事务?
关于Observer事务复用与死锁问题的解答
问题1:Observer的事务复用规则
- Observer的数据库操作默认会复用触发事件的上下文已开启的事务,无需单独配置。如果在控制器的
DB::transaction闭包内执行了模型的创建/更新操作,触发的Observer中所有数据库写入查询都会自动加入到这个外层事务中,外层事务回滚时,Observer内的操作也会同步回滚。 - 只有当你在Observer内部手动开启独立事务(自行调用
DB::transaction闭包)时,才会生成和外层隔离的独立事务上下文。
问题2:Observer未单独开启事务时的死锁解决方案
死锁本质是不同事务对资源的加锁顺序不一致导致的,Observer的操作属于触发它的上层事务的一部分,可根据场景选择以下两种方案处理:
方案1:给控制器外层事务增加重试参数
如果Observer是被控制器内的事务操作触发,直接给外层DB::transaction方法增加重试次数参数即可,Observer内的操作属于事务的一部分,会被自动纳入重试逻辑:
// 原事务写法 DB::transaction(function () { $model->update($data); // 该操作会触发对应Observer }); // 调整为带3次重试的写法,事务内发生死锁会自动回滚并重试 DB::transaction(function () { $model->update($data); }, 3);
方案2:在Observer内部套带重试的事务
如果触发Observer的模型操作没有外层事务,直接在Observer的对应事件方法中包裹带重试的事务即可:
public function updated(Model $model) { DB::transaction(function () use ($model) { // 原Observer内的所有数据库操作逻辑 }, 3); }
注意:如果上层已存在事务,该写法会自动生成嵌套事务(MySQL等主流数据库均支持),重试逻辑依然生效
补充优化建议
从根源降低死锁概率可以有效减少重试开销:统一不同业务场景的表加锁顺序,例如所有涉及订单、库存的操作都优先锁库存表再锁订单表;尽量缩小事务粒度,不要在事务内放置第三方接口调用等非数据库操作逻辑。
内容的提问来源于stack exchange,提问作者Ameerul Adib
相关产品推荐
相关产品推荐

