You need to enable JavaScript to run this app. Laravel并发提交表单触发自增列数据重复,是否需采用悲观锁?
优惠活动
大模型
产品
解决方案
定价
更多

Laravel并发提交表单触发自增列数据重复,是否需采用悲观锁?

Laravel并发提交导致自增字段重复问题的实际解决办法

先理清楚你遇到的问题本质

你碰到的双浏览器同时提交出现重复值、代码层唯一校验失效,本质是读-改-写流程的非原子竞态问题,和你代码写得严谨与否没关系:

你之前写的唯一校验逻辑是「先查当前x的存储值→计算新值→判断新值是否重复→不重复就写入」,两个并发请求几乎同时进来时,都会查到x的当前值为0,都会计算出新值为1,都会判断“当前库中没有1、校验通过”,最后先后把1写进库,自然就出现重复。
这种问题靠应用层校验永远防不住,因为校验和写入两个动作之间不是原子完成的。

悲观锁是不是必选项?—— 不是,按业务场景选最低成本的方案就好

很多人碰到并发问题第一反应是上悲观锁,其实多数场景下根本不用上这么重的方案,按优先级选实现即可:

最高优先级:用数据库原生原子自增,从根源消除竞态

如果你的逻辑就是单纯给x列加1,完全没必要搞“先读值、算新值、再写入”的拆分流程,直接用Laravel封装的increment方法,底层生成的是update 表 set x = x +1 where id = 指定ID的原子SQL,数据库自己会处理并发写入,不管多少请求同时进来,每个请求拿到的自增值都是顺序递增的,绝对不会重复,连锁都不用加。
核心代码逻辑非常简单:

// 替代你原来的“读值→算值→存值”三步操作
YourModel::where('id', 目标行ID)->increment('x');

这个方案性能最好,没有并发等待,没有锁开销,是90%这类场景的最优解。

次选方案:必须基于当前x值做自定义计算时,再考虑锁

如果你的业务不是纯自增——比如x是带规则的流水号,需要读到当前值后补前缀、算校验位、做长度格式化,不能直接用数据库原生自增,这时候再按并发量选锁方案:

  1. 低并发场景:用Laravel封装的行级悲观排他锁
    只要把整个读值、计算、写入的流程放在同一个数据库事务里,配合lockForUpdate()方法给目标行加排他锁,就能保证同一时间只有一个请求能拿到这行数据的修改权限,其他并发请求会在锁外排队,等前一个请求提交事务后再依次执行。
    参考实现:
    DB::transaction(function () {
        // 必须加上lockForUpdate(),否则锁完全不生效
        $targetRow = YourModel::where('id', 目标行ID)->lockForUpdate()->first();
        // 这里写你原来的自定义计算、业务校验逻辑
        $newX = 你的自定义计算逻辑($targetRow->x);
        $targetRow->update(['x' => $newX]);
    });
    
    注意两个容易踩的坑:
    • 锁必须和事务绑定,单独写lockForUpdate()不套事务是无效的
    • 数据库引擎必须是InnoDB,MyISAM不支持行级锁
  2. 高并发但实际写冲突少的场景:用乐观锁
    如果并发量不小,但实际同时修改同一行数据的概率很低,用乐观锁的性能比悲观锁好很多,不会让请求排队。实现方式就是给表加一个version整数列,更新的时候带上之前读到的版本号,只有数据库里的版本号和之前读的一致,才允许更新,否则就返回提示让用户重试。
    参考实现:
    $targetRow = YourModel::find(目标行ID);
    $newX = 你的自定义计算逻辑($targetRow->x);
    // 更新时带上原版本号作为校验条件
    $affectedRows = DB::table('your_table')
        ->where('id', 目标行ID)
        ->where('version', $targetRow->version)
        ->update([
            'x' => $newX,
            'version' => $targetRow->version + 1
        ]);
    // 如果影响行数为0,说明有其他请求抢先修改了数据
    if ($affectedRows === 0) {
        return back()->with('error', '操作频繁,请稍后重试');
    }
    

关于你之前写的唯一校验的优化

你之前在代码里写的unique规则永远防不住并发,只要给x列加上数据库层面的唯一索引,根本不用写应用层的校验逻辑——两个并发请求写入重复值的时候,数据库会直接抛出唯一键冲突异常,你只要在捕获异常的逻辑里给用户返回友好提示就行。
很多人写校验的时候习惯在应用层查一遍,其实只要数据库有唯一索引,哪怕你应用层啥校验都不写,也绝对不会出现重复值入库的情况,应用层的校验只是为了提前拦截、提升用户体验,不能当安全网用。

几个容易踩的坑

  • 不要把读值和写值拆成两个独立的流程,100%会出竞态问题
  • 能靠数据库原子操作解决的问题,就不要靠应用层加锁,数据库的并发处理能力比应用层高一个数量级
  • 如果你对实时性要求不高,可以把自增、写入的逻辑移到队列里串行执行,也能解决并发问题,但要额外维护队列的稳定性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.30 14:06:03