電商并發(fā)減庫存設(shè)計,如何做到不超賣
前言
高并發(fā)的秒殺活動中,通過查詢數(shù)據(jù)庫判斷是否還有庫存,然后對庫存字段進(jìn)行增減,極易出現(xiàn)庫存超出或者庫存為負(fù)的情況,一般來說有3中解決辦法(數(shù)據(jù)庫表加鎖,memche緩存,redis隊列);
我們這里使用redis來解決問題
1、思路:
1)觸發(fā)開始開團(tuán)的同時,把庫存數(shù)量更新到id對應(yīng)的隊列上去(定時更新,或者手動更新)
2)用戶請求接口,如果隊列長度>0,移除一個隊列記錄,同時對數(shù)據(jù)庫進(jìn)行相應(yīng)操作
3)如果隊列長度<=0,攔截用戶的訪問,返回‘無庫存’
2、重點(diǎn)設(shè)計在數(shù)據(jù)庫層面
2張表:
第一張:判重表(buy_record),該用戶有沒秒殺過該商品
字段: id, uid, goods_id, addtime
第二張表:商品表 goods
字段:goods_id goods_num
方案一
start transaction;
select id from buy_record where uid=$uid and goods_id=$goods_id;
if(結(jié)果不為空)
拋異常,回滾。
insert into buy_record。。。
if(受影響行數(shù)<=0)
拋異常,回滾。。。
select goods_num from goods where goods_id=$good_id;
if(庫存<=0)
拋異常,回滾。。。
update goods set goods_num=goods_num-1 where goods_id=$goods_id;
if(受影響行數(shù)<=0)
該方法在高并發(fā)下幾乎必然導(dǎo)致超賣。當(dāng)庫存為1的時候剛好多個用戶同時 select goods_num from goods where goods_id=$good_id;此時庫存剛好大于0,做update操作的時候必然減到小于0. 同時上面進(jìn)行是否秒殺過的判重同樣會出現(xiàn)類似問題
方案二
start transaction;
select id from buy_record where uid=$uid and goods_id=$goods_id for update ;
if(結(jié)果不為空)
拋異常,回滾。
insert into buy_record。。。
if(受影響行數(shù)<=0)
拋異常,回滾。。。
select goods_num from goods where goods_id=$good_id for update ;
if(庫存<=0)
拋異常,回滾。。。
update goods set goods_num=goods_num-1 where goods_id=$goods_id ;
if(受影響行數(shù)<=0)
拋異常,回滾。。。
該方法有效的防止了超賣,但是在每次select的時候加上了排它鎖,每次select操作都會被堵塞 ,并發(fā)性能大大降低。
方案三
對(uid,goods_id)加唯一索引?。?
start transaction;
insert into buy_record。。。
if(唯一索引報錯?)
拋異常,已經(jīng)秒過了,回滾。。。
update goods set goods_num=goods_num-1 where goods_id=$goods_id and goods_num>0 ;
if(受影響行數(shù)<=0)
拋異常,商品秒完了,回滾。。。
該方法完美的解決了超賣與select排它鎖導(dǎo)致的并發(fā)低的問題,并且4個sql縮減成2個sql語句。極大提升性能。