高并发超卖问题:测试出现超卖问题和解决方案

说明:当前测试为thinkphp5环境下的代码、不考虑用户uid问题,只考虑库存问题

 

准备:

  1. 新建两个表(goods、orders)

CREATE TABLE `goods` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL DEFAULT '',
`number` int NOT NULL DEFAULT '0',
`price` int NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=gbk

CREATE TABLE `order` (
`id` int NOT NULL AUTO_INCREMENT,
`uid` int NOT NULL DEFAULT '0',
`goods_id` int NOT NULL DEFAULT '0',
`number` int NOT NULL DEFAULT '0',
`add_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=gbk

  2.插入两条商品数据

insert into `seo`.`ctx_goods` ( `name`, `number`, `price`) values ( '苹果手机', '10', '1');
insert into `seo`.`ctx_goods` ( `name`, `number`, `price`) values ( '苹果电脑', '3', '2');

  3.购买商品业务代码

public function order(){

    $goods_id = 2;
    $number = 1;
    // 启动事务
    Db::startTrans();
    try{
        $goods = Db::name('goods')->lock(true)->find($goods_id);
        if($goods['number']>0){
            $res1 = Db::name('goods')->where(['id' => $goods_id])->setDec('number', $number);
            $orderData = [
                'goods_id' => $goods['id'],
                'uid' => 1,
                'number' => $number,
            ];
            $res2 = Db::name('order')->insert($orderData);
            if($res1 !==false && $res2){
                // 提交事务
                Db::commit();    
                exit('ok');
            }else{
                throw new \Exception(' 下单失败');
            }
        }else{
            throw new \Exception(' 库存不足');
        }
    } catch (\Exception $e) {
        // 回滚事务
        Db::rollback();
        exit('error');
    }
    exit;
}

  4. 测试产生超卖问题

  注意:ab测试工具如果没有安装请百度搜索ab工具安装

ab -c500 -n800 -k http://seo_ctx.cn/portal/index/order

  5. 结果

  商品表数据

    

 

 

 

  订单表数据

    

   

  出现问题原因:

  高并发,导致mysql查询同出来的数据一样,而在同一时间修改没有立即生效,之后这些修改都会被执行。

  从而出现,库存只有3个却生成了3个以上的订单

 

 

  解决方案:

  1.设置数据库行锁

  使用行锁,限制同一时间只能进行一个数据修改操作

将
$goods = Db::name('goods')->find($goods_id);
改成
$goods = Db::name('goods')->lock(true)->find($goods_id);

  2.使用redis

  用redis去维护库存

  • redis是基于内存的,内存的读写速度非常快;
  • redis是单线程的,省去了很多上下文切换线程的时间;

 

public function order(){
    $goods_id = 2;
    $number = 1;
    $goods = Db::name('goods')->find($goods_id);

    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379);
    if($redis->get('goodsNumber:'.$goods_id)!==false){
        //设置初始库存
        $redis->set('goodsNumber:'.$goods_id, $goods['number']);
    }
    // 启动事务
    Db::startTrans();
    try{
        $remain_number = $redis->get('goodsNumber:'.$goods_id);
        if($remain_number>0){
            $redis->decr('goodsNumber:'.$goods_id, 1);
            $res1 = Db::name('goods')->where(['id' => $goods_id])->setDec('number', $number);
            $orderData = [
                'goods_id' => $goods_id,
                'uid' => 1,
                'number' => $number,
            ];
            $res2 = Db::name('order')->insert($orderData);

            
            if($res1 !==false && $res2){
                // 提交事务
                Db::commit();
                exit('ok');
            }else{
                throw new \Exception(' 下单失败');
            }
        }else{
            throw new \Exception(' 库存不足');
        }
    } catch (\Exception $e) {
        // 回滚事务
        Db::rollback();
        exit('error');
    }
    exit;
}

  

 

  

posted @ 2021-02-23 10:20  小绵羊~~  阅读(686)  评论(0编辑  收藏  举报