高並發超賣問題:測試出現超賣問題和解決方案


說明:當前測試為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;
}

  

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM