先普及一下,什麼叫超賣,訂單商品數據量大於商品庫存數量,就叫做超賣;
那麼問題來了,爲什麼會超賣呢?
在商城搶購中,假如庫存爲100個,這時有100 000個併發請求過來了,最後庫存只剩1個時,假如還有1000個併發請求,如果這1000個請求都成功了,那最後庫存是不是變成了-999,這就是超賣。(以上僅爲理論上理解超賣)
先介紹一個很有用的工具,併發測試工具ab.exe,在Apache的bin目錄下打開cmd窗口,輸入命令ab -n10000 -c1000 http://localhost/code/mysql.php,表示測試10000個請求,併發量爲1000,測試訪問地址爲http://localhost/code/mysql.php
先建一個商品表,store爲庫存,以下爲一些用到SQL語句
// 建表
create table product(
id int(8) not null auto_increment comment '產品id',
name varchar(25) not null default '' comment '產品名',
price decimal(10,3) not null default 0 comment '價格',
store int(8) not null default 0 comment '庫存',
primary key(id)
)engine=innodb default charset=utf8;
// 添加數據
insert into product value(1, '小米手環', 198, 10);
// 查看錶數據
select * from product;
// 設置庫存
update product set store=10 where id=1;
接下來,用代碼解釋下高併發搶購的超賣,直接在MySQL上測試,以下就是mysql.php文件
<?php
header("content-type:text/html; charset=utf-8");
// 連接數據庫
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'root') or die("數據庫連接失敗");
$pdo->exec("set names utf8");
$sql = "select * from product where id=1";
$res = $pdo->query($sql);
$data = $res->fetch(PDO::FETCH_ASSOC);
// print_r($data);
if($data['store']>0){
// 開啓事務
$pdo->beginTransaction();
$sql = "update product set store=store-1 where id=1";
$res = $pdo->query($sql);
print_r($res);
if($res){
echo "搶到啦!";
}else{
echo "搶購失敗...";
$pdo->rollBack();
}
$pdo->commit();
}else{
echo '商品已被搶購一空,感謝參與!';
}
?>
給它10000個請求,併發爲1000
來看下商品表中的庫存store
再來看下用redis實現的,用到了redis的事務、樂觀鎖,以下爲redis.php
<?PHP
header("content-type:text/html; charset=utf-8");
// 連接數據庫
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'root') or die("數據庫連接失敗");
$pdo->exec("set names utf8");
$redis = new Redis();
$redis->connect("localhost");
$redis->watch('salec'); // 虛擬銷量
$salec = $redis->get('salec');
$store = 10; // 設置虛擬庫存與數據庫的一致
if($salec >= $store){
exit("商品已被搶購一空,感謝參與!");
}
// 開啓事務
$redis->multi();
$redis->incr('salec'); // 增加銷量
$res = $redis->exec(); // 執行事務 如果發現自己拿到的key被修改了,事務則被打斷
if($res){
// 以下代碼只是爲了測試,實際中是交給 MQ 去處理
// 連接數據庫
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'root') or die("數據庫連接失敗");
$pdo->exec("set names utf8");
$sql = "update product set store=store-1 where id=1";
if($pdo->query($sql)){
echo "搶到啦!";
}
}else{
echo "搶購失敗...";
}
?>
也給它10000個請求,併發爲1000
看下商品表中的庫存store
redis完美解決了高併發搶購的超賣現象。