Laravel实现跨多订单批次按可用量扣减商品库存的查询方案
多订单来源同商品库存扣减实现方案
完全可以通过数据库层面的原子操作+分批扣减逻辑实现需求,同时兼顾高并发下单性能、避免负库存异常。
现有代码缺陷
你当前的实现存在两个核心问题,直接导致负库存:
- 仅查询排序后的第一条库存记录,直接对单条记录执行全量下单量扣减,完全没有判断单条记录的剩余库存是否能覆盖扣减量
- 没有并发安全控制,高并发场景下会出现多个请求同时读取到同一条库存记录、重复扣减的问题
正确实现逻辑
核心思路是按优先级逐批次扣减+原子更新防超扣,具体流程:
- 开启数据库事务,保证扣减操作的原子性
- 按
id_order升序取出对应商品所有剩余库存大于0的批次记录,高并发场景下可以加lockForUpdate行锁避免并发读写冲突 - 循环遍历库存批次,每一批次最多扣减「该批次剩余库存」和「剩余待扣量」的较小值,扣减时通过SQL条件约束保证不会扣成负数
- 每扣完一个批次就核减剩余待扣量,待扣量归0时直接终止循环
- 遍历结束后如果剩余待扣量仍大于0,说明总库存不足,回滚事务返回下单失败;待扣量为0则提交事务完成扣减
可直接落地的代码示例(Laravel Eloquent)
<?php use Illuminate\Support\Facades\DB; // 开启事务 DB::beginTransaction(); try { $needDeduct = $request->quantita; $restaurantId = Auth::user()->id_restaurant; // 按id_order优先级查询所有可用库存批次,高并发场景加行锁 $stocks = warehouse::where('id_restaurant', $restaurantId) ->where('id_wine', $wine_id) ->where('quantita_restante', '>', 0) ->orderBy('id_order') ->lockForUpdate() // 高并发必加,避免同批次库存被重复读取扣减 ->get(); foreach ($stocks as $stock) { if ($needDeduct <= 0) break; // 已扣足数量直接退出 // 当前批次实际可扣数量,取批次剩余库存和待扣量的最小值 $currentDeduct = min($stock->quantita_restante, $needDeduct); // 原子更新:只有当剩余库存>=待扣量时才执行扣减,从数据库层面杜绝负库存 $affected = warehouse::where('id', $stock->id) ->where('quantita_restante', '>=', $currentDeduct) ->decrement('quantita_restante', $currentDeduct); if ($affected > 0) { // 扣减成功,核减剩余待扣量 $needDeduct -= $currentDeduct; } } if ($needDeduct > 0) { // 库存不足,回滚 DB::rollBack(); return response()->json(['msg' => '商品库存不足'], 400); } // 扣减成功,提交事务,后续可以写订单生成逻辑 DB::commit(); return response()->json(['msg' => '下单成功']); } catch (\Exception $e) { DB::rollBack(); return response()->json(['msg' => '下单失败,请重试'], 500); }
方案优势
- 完全符合按
id_order优先级扣减批次库存的需求,不会出现跨批次乱扣的情况 - 数据库层面的条件更新+事务锁机制,从根源上避免负库存、超卖问题
- 仅在事务内做必要的行级锁定,锁粒度小,不会影响高并发下的下单性能
内容的提问来源于stack exchange,提问作者alessio scionti
相关产品推荐
相关产品推荐

