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

基于FIFO的库存出库:如何批量扣减多行库存数量

库存FIFO出库多行扣减的正确处理方案

你的现有代码逻辑本身可以处理多行库存扣减的场景(比如10+5+5=20的出库需求),但存在事务缺失、并发安全、无库存校验等关键问题,以下是优化后的完整解决方案:

现有代码的执行流程(对应你的场景)

针对卖出20件、库存为10/5/5的情况,你的代码会按以下步骤执行:

  1. 读取第一条10件的库存:$quantity[$i](20)≥10,将该库存行设为0,剩余待扣减数量变为10
  2. 读取第二条5件的库存:剩余数量10≥5,将该库存行设为0,剩余待扣减数量变为5
  3. 读取第三条5件的库存:剩余数量5≥5,将该库存行设为0,剩余待扣减数量变为0,跳出循环

这个流程逻辑是对的,但一旦中间步骤出错(比如数据库异常),会导致部分库存已扣减、部分未处理的不一致状态,同时无法应对并发下单的超卖问题。

优化后的完整代码

// 开启数据库事务,确保所有出库操作原子性
DB::beginTransaction();

try {
    foreach ($product_id as $index => $productId) {
        $remainingQty = $quantity[$index];
        if ($remainingQty <= 0) continue;

        // 加悲观锁锁定库存行,防止并发修改导致超卖
        $stockEntries = DB::table('purchase_products')
            ->select('id', 'quantity', 'purchase_price')
            ->where('product_id', $productId)
            ->where('quantity', '>', 0)
            ->orderBy('created_at', 'asc')
            ->lockForUpdate()
            ->get();

        // 提前校验总库存是否足够
        $totalAvailable = $stockEntries->sum('quantity');
        if ($totalAvailable < $remainingQty) {
            throw new Exception("商品ID {$productId} 库存不足,当前仅剩余 {$totalAvailable} 件");
        }

        foreach ($stockEntries as $entry) {
            if ($remainingQty <= 0) break;

            // 计算当前行需要扣减的数量
            $deductQty = min($remainingQty, $entry->quantity);

            if ($deductQty === $entry->quantity) {
                // 扣完当前库存行
                DB::table('purchase_products')
                    ->where('id', $entry->id)
                    ->update(['quantity' => 0]);
            } else {
                // 扣减部分库存
                DB::table('purchase_products')
                    ->where('id', $entry->id)
                    ->update(['quantity' => $entry->quantity - $deductQty]);
            }

            $remainingQty -= $deductQty;
        }
    }

    // 所有操作完成后提交事务
    DB::commit();
    return redirect('sales')->with('success', '出库操作已完成');
} catch (Exception $e) {
    // 出错时回滚所有操作
    DB::rollBack();
    return redirect('sales')->with('error', $e->getMessage());
}

关键优化说明

  • 事务包裹:所有库存扣减操作在事务中执行,中途任何错误都会回滚,避免数据不一致
  • 悲观锁控制并发:使用lockForUpdate()锁定查询到的库存行,防止多个请求同时修改同一份库存导致超卖
  • 提前库存校验:先计算总可用库存,不足时直接抛出错误,避免无效循环
  • 简化逻辑:用min()函数直接计算扣减数量,减少冗余的分支判断
  • 变量可读性优化:用remainingQty替代原数组索引访问,代码更清晰

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 03:05:35