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