mysql悲观锁处理赠品库存超卖的情况

Posted 邹柯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql悲观锁处理赠品库存超卖的情况相关的知识,希望对你有一定的参考价值。

处理库存超卖的情况前,先了解下什么是乐观锁和悲观锁,下面的几篇博客已经介绍的比较详细了,我就不在赘述其原理了

【MySQL】悲观锁&乐观锁

 

对mysql乐观锁、悲观锁、共享锁、排它锁、行锁、表锁概念的理解

 

下面开始介绍悲观锁在实际中的应用了

//下订单

..........
try {
M()->startTrans();

//
判断商品是否有赠品 $give_gift=$ob->getGiveGoods($sku_nos); if(!empty($give_gift)){ $this->dealSkuGift($give_gift,$ob,$data[\'order_no\'],$skuNumArr,$packageSku); }
}catch(FlException $ex) {
M()->rollback();
$curCode = $ex->getErrorCode();
$curmsg = $ex->getMessage();
E($curCode, $curmsg);
}
M()->commit();

..........

//获取赠品库存

public function getGiveGoodsStock($sku_no){
    $sku_no=implode(",",array_unique($sku_no));
    $sku_no = "\'".str_replace(",","\',\'",$sku_no)."\'";
    $Model = new \\Think\\Model();
    $sku_gift_info=$Model->query("select sku_no,stock from ".C(\'DB_PREFIX\')."sku_gift_stock where is_deleted=0 and is_online=1 and sku_no in($sku_no) for update");
    return $sku_gift_info;
}

// 处理赠品

private function dealSkuGift($give_gift,$ob,$orderNo,$skuNumArr,$packageSku){
    if(empty($give_gift) or empty($skuNumArr)){
         return false;
    }
    $skuGiftStockM=M(\'sku_gift_stock\');
    $sku_gifts=array_column($give_gift,\'sku_no\');
    //获取赠品库存
    $sku_give_gift=$ob->getGiveGoodsStock($sku_gifts,1);
    $skuGiftStock=array();
    $giftCount=0;
    foreach($sku_give_gift as $k=>$v){
         $skuGiftStock[$v[\'sku_no\']]=$v[\'stock\'];
    }
    $public = new PModel();
    foreach ($give_gift as $v){
         $goodsNums=0;
         $delStockWhere=array();
         if($v[\'isHaveStock\']==\'1\' && $skuGiftStock[$v[\'sku_no\']] >0){//代表有货--这些是需要发货的
              if($v[\'type\']==\'1\'){
                  $lastGiftNum=$skuNumArr[$v[\'parent_sku_no\']]*$v[\'goods_nums\'];
              }else{//按固定赠品数量
                  $lastGiftNum=$v[\'goods_nums\'];
              }
              if($lastGiftNum >= $v[\'stock_num\']){
                  $lastGiftNum=$v[\'stock_num\'];
              }
              if($v[\'stock_num\']==0){
                  $lastGiftNum=0;
              }
              $delStockWhere[\'sku_no\']=$v[\'sku_no\'];
              $delStockWhere[\'is_deleted\']=0;
              $delStockWhere[\'is_online\']=1;
              $delStockWhere[\'_string\']= sprintf(\'stock>=%d\', $lastGiftNum);
              $skuGiftStockM->where($delStockWhere)->lock(true)->setDec(\'stock\',$lastGiftNum);//扣除商品赠品库存
              $re_v=$public->updataSignData($v[\'sku_no\'],$lastGiftNum,\'virtual_inventory\',\'-\',\'goods_sku\',\'sku_no\');//扣除赠品商品库存
              if(false == $re_v){
                    E(\'300110\');
              }
              $giftCount+=$lastGiftNum;
         }else{
              $lastGiftNum=0;
         }
         $dd[]=array(
              \'parent_sku_no\'=>$v[\'parent_sku_no\'],
              \'sku_no\'=>$v[\'sku_no\'],
              \'num\'=>$lastGiftNum,//最终发货的数量,无货为0
              \'create_time\'=>date(\'Y-m-d H:i:s\',time()),
              \'order_no\'=>$orderNo,
              \'package_id\'=>$packageSku[$v[\'parent_sku_no\']],
              \'stock_num\'=>empty($skuGiftStock[$v[\'sku_no\']])?0:$skuGiftStock[$v[\'sku_no\']]
         );
    }
    $order_goods_gift=M(\'order_goods_gift\');
    $order_goods_gift->addAll($dd);

    $upOrdeData=array(
         \'gift_count\'=>$giftCount+1
    );
    M(\'order\')->where(\'order_no="\'.$orderNo.\'"\')->data($upOrdeData)->save();
    return true;
}

//接口访问方式

//由于要测试并发下该接口的超卖处理情况,所以访问接口前,可将body里的参数写死到接口内,这样方便调试

//再次访问接口

//下面就可以用apache的ab工具对下单接口进行并发测试了

 到数据库里查看商品P002026-01关联了2个赠品,各关联了10个

查看赠品的库存数量

apache并发测试的原理及使用方法参见博客:https://www.cnblogs.com/lishuyi/p/5808661.html

使用方法:

ab -n 10 -c 5  http://app.zouke.com/ 
(-n发出10个请求,-c模拟5个并发,相当5人同时访问,后面是测试url)

ab -t 60 -c 100 http://192.168.0.10/ 
在60秒内发请求,一次100个请求。 

先预测下,请求10次,并发5个,最终的库存会剩余0

下面开始并发测试

最终赠品P002872、P002962的剩余库存如下

结果可见,本次悲观锁起了效果,下面看下不用悲观锁的情况会是什么样,下面将2个赠品的库存都恢复为10个,并将获取赠品库存代码进行改造,这次获取赠品库存是没加悲观锁的

//获取赠品库存
public function getGiveGoodsStock($sku_no,$ori){
    $sku_gift=M(\'sku_gift_stock\');
    $sgg_where[\'sku_no\']=array(\'in\',$sku_no);
    $sgg_where[\'is_deleted\']=0;
    $sgg_where[\'is_online\']=1;
    $sku_gift_info=$sku_gift->field(\'sku_no,stock\')->where($sgg_where)->select();
    return $sku_gift_info;
}

再次并发测试一下

发现赠品库存是变为负数了,这是超卖的情况出现了

好了,以上测试可以了,下面看下直接用操作数据表看下效果,按如下顺序执行,发现当执行完前4步后,用户B查询不到赠品库存信息,主要是因为此时赠品表已经锁住了

下面执行用户A第5步COMMIT,用户B查询赠品库存立马查询出来了

上述可见,mysql悲观锁对赠品库存超卖的处理流程的生效过程了

 

以上是关于mysql悲观锁处理赠品库存超卖的情况的主要内容,如果未能解决你的问题,请参考以下文章

使用乐观锁解决电商中商品超卖的问题

转 mysql处理高并发,防止库存超卖

MySQL处理高并发,防止库存超卖

分布式锁-电商防超卖的N+1个坑!

解决商品超卖问题

mysql处理高并发,防止库存超卖