面試官問:如何防超賣,有幾種實(shí)現(xiàn)方式
場(chǎng)景

第一種方法 悲觀鎖
悲觀并發(fā)控制(又名 “悲觀鎖”,Pessimistic Concurrency Control,縮寫 “PCC”)是一種并發(fā)控制的方法。它可以阻止一個(gè)事務(wù)以影響其他用戶的方式來修改數(shù)據(jù)。如果一個(gè)事務(wù)執(zhí)行的操作讀某行數(shù)據(jù)應(yīng)用了鎖,那只有當(dāng)這個(gè)事務(wù)把鎖釋放,其他事務(wù)才能夠執(zhí)行與該鎖沖突的操作。
悲觀并發(fā)控制主要用于數(shù)據(jù)爭(zhēng)用激烈的環(huán)境,以及發(fā)生并發(fā)沖突時(shí)使用鎖保護(hù)數(shù)據(jù)的成本要低于回滾事務(wù)的成本的環(huán)境中。
竹子,公眾號(hào):碼農(nóng)編程進(jìn)階筆記php程序員面試題(偏中級(jí)面試題)
簡(jiǎn)而言之,悲觀鎖主要用于保護(hù)數(shù)據(jù)的完整性。當(dāng)多個(gè)事務(wù)并發(fā)執(zhí)行時(shí),某個(gè)事務(wù)對(duì)數(shù)據(jù)應(yīng)用了鎖,則其他事務(wù)只能等該事務(wù)執(zhí)行完了,才能進(jìn)行對(duì)該數(shù)據(jù)進(jìn)行修改操作。
update goods set num = num - 1 WHERE id = 1001 and num > 0
假設(shè)現(xiàn)在商品只剩下一件了,此時(shí)數(shù)據(jù)庫中 num = 1;
但有 100 個(gè)線程同時(shí)讀取到了這個(gè) num = 1,所以 100 個(gè)線程都開始減庫存了。
但你會(huì)最終會(huì)發(fā)覺,其實(shí)只有一個(gè)線程減庫存成功,其他 99 個(gè)線程全部失敗。
需要注意的是,F(xiàn)OR UPDATE 生效需要同時(shí)滿足兩個(gè)條件時(shí)才生效:
數(shù)據(jù)庫的引擎為 innoDB
操作位于事務(wù)塊中(BEGIN/COMMIT)
悲觀鎖采用的是「先獲取鎖再訪問」的策略,來保障數(shù)據(jù)的安全。但是加鎖策略,依賴數(shù)據(jù)庫實(shí)現(xiàn),會(huì)增加數(shù)據(jù)庫的負(fù)擔(dān),且會(huì)增加死鎖的發(fā)生幾率。此外,對(duì)于不會(huì)發(fā)生變化的只讀數(shù)據(jù),加鎖只會(huì)增加額外不必要的負(fù)擔(dān)。在實(shí)際的實(shí)踐中,對(duì)于并發(fā)很高的場(chǎng)景并不會(huì)使用悲觀鎖,因?yàn)楫?dāng)一個(gè)事務(wù)鎖住了數(shù)據(jù),那么其他事務(wù)都會(huì)發(fā)生阻塞,會(huì)導(dǎo)致大量的事務(wù)發(fā)生積壓拖垮整個(gè)系統(tǒng)。
第二種辦法 樂觀鎖
select version from goods WHERE id= 1001;
update goods set num = num - 1, version = version + 1 WHERE id= 1001 AND num > 0 AND version = @version(上面查到的version);這種方式采用了版本號(hào)的方式,其實(shí)也就是 CAS 的原理。
假設(shè)此時(shí) version = 100, num = 1; 100 個(gè)線程進(jìn)入到了這里,同時(shí)他們 select 出來版本號(hào)都是 version = 100。
然后直接 update 的時(shí)候,只有其中一個(gè)先 update 了,同時(shí)更新了版本號(hào)。
那么其他 99 個(gè)在更新的時(shí)候,會(huì)發(fā)覺 version 并不等于上次 select 的 version,就說明 version 被其他線程修改過了。那么我就放棄這次 update
第三種方法 redis 消息隊(duì)列
在秒殺的情況下,高頻率的去讀寫數(shù)據(jù)庫,會(huì)嚴(yán)重造成性能問題。所以必須借助其他服務(wù), 利用 redis 的單線程預(yù)減庫存。比如商品有 100 件。那么我在 redis 存儲(chǔ)一個(gè) k,v。例如
每一個(gè)用戶線程進(jìn)來,key 值就減 1,等減到 0 的時(shí)候,全部拒絕剩下的請(qǐng)求。
那么也就是只有 100 個(gè)線程會(huì)進(jìn)入到后續(xù)操作。所以一定不會(huì)出現(xiàn)超賣的現(xiàn)象。
第四種辦法 redis 分布式鎖
$expire = 10;//有效期10秒
$key = 'lock';//key
$value = time() + $expire;//鎖的值 = Unix時(shí)間戳 + 鎖的有效期
$lock = $redis->setnx($key, $value);
//判斷是否上鎖成功,成功則執(zhí)行下步操作
if(!empty($lock))
{
//下單邏輯...
}
來自:https://learnku.com/articles/49280
