• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
孙龙 程序员
少时总觉为人易,华年方知立业难
博客园    首页    新随笔    联系   管理    订阅  订阅
B端业务仓储系统扣减库存更新库存汇总出现mysql Deadlock found when trying to get lock死锁怎么办

在B端业务中由于业务流转繁琐,所处事物或者嵌套事务很长,经常由于程序员代码书写不规范或者经验不足等问题出现类似 Deadlock found when trying to get lock的报错,那该如何去避免呢?

{
  "message": "SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction (SQL: delete from `lie_stock_summary` where `id` in (30498))",
  "file": "/data/wwwroot/wms.ichunt.net/vendor/laravel/framework/src/Illuminate/Database/Connection.php",
  "line": 671,
  "code": "40001"
}

来看下面一段代码,注意红色代码区域

    /**
     * Notes:修改或删除库存信息  复核提交,移位完成,调拨完成 后触发
     * 原始库位扣减操作
     * User: sl
     * Date: 2023-04-11 17:31
     * @param $data
     * @param $type 1,复核完成   2调拨出库完成后扣减   3,源库位移位完成后
     * 移位不记录日志
     * @return bool
     **************二位数组*************************
     * [["id"=>"库存id","reduceTotalQty"=>"减少的库存总数量"]]
     *
     * [["id"=>"1","reduceTotalQty"=>"50"]]
     */
    public static function updateOrDelStock($operator,$type,$data=[])
    {
        \Log::channel("stockLock")->info("---------------修改库存信息------------------");
        \Log::channel("stockLock")->info(sprintf("操作人:%s",json_encode($operator,JSON_UNESCAPED_UNICODE)));
        \Log::channel("stockLock")->info(sprintf("参数:%s",json_encode($data)));
        $arr = [];
        foreach($data as $item){
            if(!isset($arr[$item["id"]])){
                $arr[$item["id"]] = 0;
            }
            $arr[$item["id"]] += $item["reduceTotalQty"];
        }
        $stockIds = array_keys($arr);
        $stockList = StockModel::getStockListByids($stockIds);
        $stockList = arrayChangeKeyByField($stockList,"id");
        try{
            self::startTransaction();
            foreach($stockList as $id=>$stockInfo){
                $reduceQty = $arr[$id] ?? 0;
                if($reduceQty <= 0){
                    continue;
                }
                if($stockInfo["total_qty"] < $reduceQty){
                    throw new InvalidRequestException(sprintf("库存id:%s,扣减库存失败,扣减数量不能大于库存总数量",$id));
                }

                if($stockInfo["useable_qty"] < $reduceQty){
                    throw new InvalidRequestException(sprintf("库存id:%s,扣减库存失败,扣减数量不能大于库存可用数量",$id));
                }

                $totalQty = $stockInfo["total_qty"] - $reduceQty;
                $useableQty = $stockInfo["useable_qty"] - $reduceQty;
                //开始扣减库存
                $update=[];
                $update["total_qty"] = $totalQty;
                $update["useable_qty"] = $useableQty;
                $update["update_uid"] = $operator["operator_id"] ?? 0;
                $update["update_name"] = $operator["operator_name"] ?? "";
                $update["update_time"] = time();
                $update["amount"] = \DB::raw("ROUND(purchase_prices*total_qty,2)");
                $update["standard_money_amount"] = \DB::raw("ROUND(standard_money_prices*total_qty,2)");
                $update["purchase_withoutamount"] = \DB::raw("ROUND(purchase_without_tax_price*total_qty,2)");
                StockModel::where("id",$id)->update($update);
//                if($totalQty ==  0  && $useableQty == 0){
//                    //进行库存汇总
//                    self::updateOrCreateStockSummary([$id]);
//                    //删除
//                    StockModel::where("id",$id)->delete();
//
//                }

                //进行库存汇总
                self::updateOrCreateStockSummary([$id]);
                //删除
                if($totalQty ==  0  && $useableQty == 0){
                    StockModel::where("id",$id)->delete();
                }

            }
            self::commitTransaction();
        }catch (\Throwable $e){
            self::rollBackTransaction();
            throw new InvalidRequestException($e->getMessage());
        }

    }

 

    /**
     * Notes:创建库存汇总
     * 根据库存  组织+仓库+商品编码   分组统计
     * User: sl
     * Date: 2023-04-15 14:29
     * @param $stockSummaryIds
     */
    public static function updateOrCreateStockSummary($stockIds=[])
    {
        //查询库存
        $stockList = StockModel::getStockListByids($stockIds);



        $arr = [];//分组 采购组织+仓库+商品编码=>[库存列表]二位数组
        foreach($stockList as $stock){
            //采购组织+仓库+商品编码 维度统计库存
            $key = sprintf("%s_@@@_%s_@@@_%s",$stock["company_id"],$stock["warehouse_id"],$stock["goods_sn"]);
            $arr[] = $key;
        }
        $arr = array_filter_unique($arr);
        $groupData = [];//分组     采购组织+仓库+商品编码=>[库存列表]二位数组
        foreach($arr as $groupStockeyword){
            $map = explode("_@@@_",$groupStockeyword);
            if(count($map) != 3){
                continue;
            }
            $stockListSearch = StockModel::getStockByGroupStockeyword($map[0],$map[1],$map[2]);
            //找出相同组织 仓库 商品编码的库存信息
            $groupData[$groupStockeyword] = $stockListSearch;
        }
        //统计所有库存现在的锁库数量
        $tmpStockLockQtyByStockIds = \Arr::pluck($groupData,"*.id");
        $stockLockQtyByStockIds = [];
        foreach($tmpStockLockQtyByStockIds as $itemStockIds){
            $stockLockQtyByStockIds += $itemStockIds;
        }
        $stockLockQtyByStockIds = array_filter_unique($stockLockQtyByStockIds);
        $stockLockQty = StockLockModel::getStockLockQtyByStockIds($stockLockQtyByStockIds);
        $stockLockQty = arrayChangeKeyByField($stockLockQty,"stock_id");

        $filterData = [];//组装需要插入或者更新的数据   采购组织+仓库+商品编码=>库存信息 一位数组
        foreach($groupData as $groupStockeyword=>$stockListVal){
            foreach($stockListVal as $stockInfo){
                if(!isset($filterData[$groupStockeyword])){
                    $filterData[$groupStockeyword] = [];
                }
                if(empty($filterData[$groupStockeyword])){
                    $filterData[$groupStockeyword]["company_id"] = $stockInfo["company_id"];
                    $filterData[$groupStockeyword]["company_name"] = $stockInfo["company_name"];
                    $filterData[$groupStockeyword]["warehouse_id"] = $stockInfo["warehouse_id"];
                    $filterData[$groupStockeyword]["goods_id"] = $stockInfo["goods_id"];
                    $filterData[$groupStockeyword]["goods_sn"] = $stockInfo["goods_sn"];
                    $filterData[$groupStockeyword]["goods_name"] = $stockInfo["goods_name"];
                    $filterData[$groupStockeyword]["brand_id"] = $stockInfo["brand_id"];
                    $filterData[$groupStockeyword]["brand_name"] = $stockInfo["brand_name"];
                    $filterData[$groupStockeyword]["goods_unit"] = $stockInfo["goods_unit"];
                    $filterData[$groupStockeyword]["total_qty"] = $stockInfo["total_qty"];//库存总数量
                    $filterData[$groupStockeyword]["useable_qty"] = $stockInfo["useable_qty"];//可用库存数量
                    $filterData[$groupStockeyword]["lock_qty"] = $stockLockQty[$stockInfo["id"]]["total_lock_qty"] ?? 0;
                    $filterData[$groupStockeyword]["pick_stock_qty"] = $stockInfo["pick_stock_qty"];//拣货数量
                    $filterData[$groupStockeyword]["freeze_stock_qty"] = $stockInfo["freeze_stock_qty"];//冻结数量
                    $filterData[$groupStockeyword]["standard_money_amount"] = $stockInfo["standard_money_amount"];//本位币总金额
                    $filterData[$groupStockeyword]["standard_money_currency"] = $stockInfo["standard_money_currency"];//本位币
                    $filterData[$groupStockeyword]["create_uid"] = getAdminUserId();
                    $filterData[$groupStockeyword]["create_name"] = getAdminUserName();
                    $filterData[$groupStockeyword]["create_time"] = time();
                }else{
                    $filterData[$groupStockeyword]["total_qty"] += $stockInfo["total_qty"];//库存总数量
                    $filterData[$groupStockeyword]["useable_qty"] += $stockInfo["useable_qty"];//可用库存数量
                    $filterData[$groupStockeyword]["lock_qty"] += $stockLockQty[$stockInfo["id"]]["total_lock_qty"] ?? 0;
                    $filterData[$groupStockeyword]["pick_stock_qty"] += $stockInfo["pick_stock_qty"];//拣货数量
                    $filterData[$groupStockeyword]["freeze_stock_qty"] += $stockInfo["freeze_stock_qty"];//冻结数量
                    $filterData[$groupStockeyword]["standard_money_amount"] += $stockInfo["standard_money_amount"];//本位币总金额
                }

            }
        }
        //准备插入或者更新数据
        foreach($filterData as $stockSummary){
            $updateData["company_id"] = $stockSummary["company_id"];
            $updateData["warehouse_id"] = $stockSummary["warehouse_id"];
            $updateData["goods_sn"] = $stockSummary["goods_sn"];
            $insertData = $stockSummary;
            \Arr::forget($insertData, 'company_id');
            \Arr::forget($insertData, 'warehouse_id');
            \Arr::forget($insertData, 'goods_sn');
            StockSummaryModel::updateOrCreate($updateData,$insertData);
            if($insertData["total_qty"] <= 0){
                StockSummaryModel::delStockSummary($updateData);
            }
        }

    }

上述红色代码区域在并发情况下经常会出现Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction数据库报错

优化后代码:

//准备插入或者更新数据
        foreach($filterData as $stockSummary){
            $updateData["company_id"] = $stockSummary["company_id"];
            $updateData["warehouse_id"] = $stockSummary["warehouse_id"];
            $updateData["goods_sn"] = $stockSummary["goods_sn"];
            $insertData = $stockSummary;
            \Arr::forget($insertData, 'company_id');
            \Arr::forget($insertData, 'warehouse_id');
            \Arr::forget($insertData, 'goods_sn');
            if($insertData["total_qty"] <= 0 && StockSummaryModel::existsStockSummary($updateData)){
                $stockSummaryIds = StockSummaryModel::getStockSummary($updateData);
                StockSummaryModel::delStockSummaryByIds($stockSummaryIds);
            }else{
                StockSummaryModel::updateOrCreate($updateData,$insertData);
            }

        }

 

 /**
     * Notes:修改或删除库存信息  复核提交,移位完成,调拨完成 后触发
     * 原始库位扣减操作
     * User: sl
     * Date: 2023-04-11 17:31
     * @param $data
     * @param $type 1,复核完成   2调拨出库完成后扣减   3,源库位移位完成后
     * 移位不记录日志
     * @return bool
     **************二位数组*************************
     * [["id"=>"库存id","reduceTotalQty"=>"减少的库存总数量"]]
     *
     * [["id"=>"1","reduceTotalQty"=>"50"]]
     */
    public static function updateOrDelStock($operator,$type,$data=[])
    {
        \Log::channel("stockLock")->info("---------------修改库存信息------------------");
        \Log::channel("stockLock")->info(sprintf("操作人:%s",json_encode($operator,JSON_UNESCAPED_UNICODE)));
        \Log::channel("stockLock")->info(sprintf("参数:%s",json_encode($data)));
        $arr = [];
        foreach($data as $item){
            if(!isset($arr[$item["id"]])){
                $arr[$item["id"]] = 0;
            }
            $arr[$item["id"]] += $item["reduceTotalQty"];
        }
        $stockIds = array_keys($arr);
        $stockList = StockModel::getStockListByids($stockIds);
        $stockList = arrayChangeKeyByField($stockList,"id");
        try{
            self::startTransaction();
            $isToStockSummaryIds = [];//需要重新更新或者删除的库存汇总的库存id
            $isToDelStockIds = [];//扣减库存后需要删除的库存
            foreach($stockList as $id=>$stockInfo){
                $reduceQty = $arr[$id] ?? 0;
                if($reduceQty <= 0){
                    continue;
                }
                if($stockInfo["total_qty"] < $reduceQty){
                    throw new InvalidRequestException(sprintf("库存id:%s,扣减库存失败,扣减数量不能大于库存总数量",$id));
                }

                if($stockInfo["useable_qty"] < $reduceQty){
                    throw new InvalidRequestException(sprintf("库存id:%s,扣减库存失败,扣减数量不能大于库存可用数量",$id));
                }

                $totalQty = $stockInfo["total_qty"] - $reduceQty;
                $useableQty = $stockInfo["useable_qty"] - $reduceQty;
                //开始扣减库存
                $update=[];
                $update["total_qty"] = \DB::raw("total_qty-{$reduceQty}");
                $update["useable_qty"] = \DB::raw("useable_qty-{$reduceQty}");;
                $update["update_uid"] = $operator["operator_id"] ?? 0;
                $update["update_name"] = $operator["operator_name"] ?? "";
                $update["update_time"] = time();
                $update["amount"] = \DB::raw("ROUND(purchase_prices*total_qty,2)");
                $update["standard_money_amount"] = \DB::raw("ROUND(standard_money_prices*total_qty,2)");
                $update["purchase_withoutamount"] = \DB::raw("ROUND(purchase_without_tax_price*total_qty,2)");
                $bk = StockModel::where("id",$id)->where("total_qty",$stockInfo["total_qty"])->where("useable_qty",$stockInfo["useable_qty"])->update($update);
                if(!$bk){
                    throw new InvalidRequestException(sprintf("库存id:%s,扣减库存失败:可能存在相同库存数据重复扣减或者并发扣减情况",$id));
                }

                //进行库存汇总
                array_push($isToStockSummaryIds,$id);
                //删除
                if($totalQty ==  0  && $useableQty == 0){
                    array_push($isToDelStockIds,$id);
                }

            }
            self::updateOrCreateStockSummary($isToStockSummaryIds);
            if(!empty($isToDelStockIds)){
                StockModel::delStockByIds($isToDelStockIds);
            }
            self::commitTransaction();
        }catch (\Throwable $e){
            \Log::channel("stockLock")->info(json_encode(ErrMsg::getExceptionInfo($e)));
            self::rollBackTransaction();
            throw new InvalidRequestException($e->getMessage());
        }

    }

优化代码思路

1:explain 查看sql,更新代码的where条件是否有索引或者是否用的主键id更新,统一更改为用主键更新或者删除更稳妥

2,更新或者删除是否在循环中存在互斥条件

 

本文来自博客园,作者:孙龙-程序员,转载请注明原文链接:https://www.cnblogs.com/sunlong88/p/17541057.html

posted on 2023-07-10 14:17  孙龙-程序员  阅读(813)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3