You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel使用lockForUpdate后关联Bars仍为旧值的问题及解决

问题原因及解决方案

原因分析

  1. 可重复读隔离级别的快照特性:MySQL默认采用REPEATABLE READ(可重复读)隔离级别,事务启动时会生成一致性快照,事务内所有查询均基于该快照返回数据。若第二次事务在第一次事务提交前启动,即便主查询(Foo表)因lockForUpdate等待至第一次事务提交后执行,关联的bars查询仍会使用事务启动时的旧快照,导致获取到旧数据。
  2. 关联查询未同步加锁:lockForUpdate仅作用于主查询的Foo行数据,而with('bars')触发的关联查询未加锁控制。并发场景下,bars的查询可能在第一次事务完成更新前就执行完毕,提前获取了旧数据。

解决方案

方案1:调整事务隔离级别为读已提交

将当前事务的隔离级别改为READ COMMITTED,使事务内的查询始终读取已提交的最新数据,而非启动时的快照。可在事务内临时设置:

DB::transaction(function () {
    DB::statement('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
    
    $foo = Foo::whereId(1)->lockForUpdate()->with('bars')->first();
    // 业务逻辑:打印、更新字段
});

注意:不建议全局修改数据库隔离级别,避免影响其他依赖可重复读特性的业务。

方案2:给关联查询也添加行锁

通过闭包给关联查询加上lockForUpdate,确保主查询和关联查询同步等待前序事务提交,获取一致的最新数据:

DB::transaction(function () {
    $foo = Foo::whereId(1)->lockForUpdate()
        ->with(['bars' => function ($query) {
            $query->lockForUpdate();
        }])->first();
    
    // 业务逻辑:打印、更新字段
});

方案3:拆分查询,手动加载关联

先查询主模型(等待锁释放获取最新Foo数据),再手动调用load方法加载关联,强制从数据库读取最新的bars数据:

DB::transaction(function () {
    $foo = Foo::whereId(1)->lockForUpdate()->first();
    $foo->load('bars'); // 此时加载的bars为最新数据
    
    // 业务逻辑:打印、更新字段
});

内容的提问来源于stack exchange,提问作者Cositanto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 10:55:17