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