圖解MySQL中樂(lè)觀鎖扣減庫(kù)存原理
1 基礎(chǔ)知識(shí)
在電商系統(tǒng)中扣減庫(kù)存是一步非常關(guān)鍵的操作,例如秒殺系統(tǒng)中一定要防止超賣(mài)情況出現(xiàn),如果商家設(shè)置了100件庫(kù)存但是最后賣(mài)出1000件,這樣就會(huì)產(chǎn)生資金損失。在扣減庫(kù)存時(shí)一般使用如下語(yǔ)句:
udpate goods set stock = stock - #{acquire} where sku_id = #{skuId} and stock - #{acquire} >= 0
這條語(yǔ)句可以保護(hù)庫(kù)存資源防止超賣(mài),我們不妨分析這條語(yǔ)句為什么生效。本文使用MySQL Innodb引擎進(jìn)行演示,隔離級(jí)別為可重復(fù)讀。
1.1 共享鎖與排它鎖
共享鎖(share Lock)又被稱為讀鎖,實(shí)現(xiàn)共享鎖語(yǔ)句如下:
select lock in share mode
排它鎖(exclusive Lock)又被稱為寫(xiě)鎖,實(shí)現(xiàn)排它鎖語(yǔ)句如下:
select for update update delete insert
共享鎖與排它鎖兼容關(guān)系如下表:
我們通過(guò)實(shí)例分析上述兼容關(guān)系,首先建一張測(cè)試表并寫(xiě)入測(cè)試數(shù)據(jù):
CREATE TABLE `test_account` ( `id` bigint(20) NOT NULL, `name` varchar(20) DEFAULT NULL, `account` bigint(20) DEFAULT NULL, `version` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `test_account`(`id`,`name`,`account`,`version`) values (1,'A',100,1); insert into `test_account`(`id`,`name`,`account`,`version`) values (2,'B',200,1); insert into `test_account`(`id`,`name`,`account`,`version`) values (3,'C',300,1);
(1) 讀讀兼容
共享鎖與共享鎖之間兼容,在如下實(shí)例中session1在t3時(shí)刻,session2在t4時(shí)刻執(zhí)行查詢均可以獲取預(yù)期結(jié)果:
(2) 讀寫(xiě)互斥
共享鎖與排它鎖之間互斥,在如下實(shí)例中session1在t3時(shí)刻加共享鎖,可以正確讀取結(jié)果,但是session2在t4時(shí)刻嘗試加排它鎖,但是此時(shí)鎖被session1占有,session2需要等待,當(dāng)session1長(zhǎng)時(shí)間不釋放鎖時(shí),session2拋出鎖超時(shí)異常:
(3) 寫(xiě)寫(xiě)互斥
排它鎖與排它鎖之間互斥,在如下實(shí)例中session1在t3時(shí)刻加排它鎖,可以正確讀取結(jié)果,但是session2在t4時(shí)刻嘗試加排它鎖,但是此時(shí)鎖被session1占有,session2需要等待,當(dāng)session1長(zhǎng)時(shí)間不釋放鎖時(shí),session2拋出鎖超時(shí)異常:
1.2 當(dāng)前讀與快照讀
MySQL Innodb存儲(chǔ)引擎實(shí)現(xiàn)基于多版本并發(fā)控制協(xié)議MVCC,在MVCC并發(fā)控制中讀操作可以分成快照讀與當(dāng)前讀。
快照讀不需要加鎖,讀取的是記錄可見(jiàn)版本,有可能是歷史版本??梢灶?lèi)比訂單快照,用戶下單之后商品價(jià)格發(fā)生了變化,但是訂單快照不會(huì)改變。實(shí)現(xiàn)當(dāng)前讀語(yǔ)句如下:
select
當(dāng)前讀需要加鎖,讀取的是記錄最新版本,加鎖保證了在讀取時(shí),當(dāng)前記錄不會(huì)被其它事務(wù)修改。實(shí)現(xiàn)當(dāng)前讀語(yǔ)句如下:
select lock in share mode select for update update delete insert
我們通過(guò)一個(gè)實(shí)例分析快照讀和當(dāng)前讀,session2在t4時(shí)刻修改記錄并在t5時(shí)刻提交,session1在t6時(shí)刻進(jìn)行了快照讀,讀取的是本事務(wù)開(kāi)始時(shí)結(jié)果100,在t7時(shí)刻進(jìn)行了當(dāng)前讀,讀取的是記錄最新版本結(jié)果101:
當(dāng)前讀流程是怎么樣的呢?我們以u(píng)pdate為例進(jìn)行分析當(dāng)前讀流程:
第一次程序?qū)嵗l(fā)出當(dāng)前讀請(qǐng)求,存儲(chǔ)引擎返回滿足where條件的第一條記錄并加鎖,程序?qū)嵗侔l(fā)出更新請(qǐng)求,存儲(chǔ)引起操作完成響應(yīng)成功。依次執(zhí)行直到所有滿足where條件記錄執(zhí)行完成為止。
這里我們做一些引申,RR級(jí)別提供了兩種機(jī)制避免幻讀問(wèn)題:第一種方式是快照讀,讀取的是當(dāng)前事務(wù)開(kāi)啟時(shí)的快照。第二種方式針對(duì)當(dāng)前讀,防止幻讀依賴Next-Key Lock機(jī)制。
2 樂(lè)觀鎖原理
我們通過(guò)一個(gè)問(wèn)題將上述知識(shí)整合起來(lái):有兩個(gè)線程在同一時(shí)刻執(zhí)行如下語(yǔ)句,請(qǐng)問(wèn)id=1這條記錄account值會(huì)不會(huì)成功扣減兩次?
update test_account set account = account - 100, version = version + 1 where id = 1 and version = 1
上述語(yǔ)句使用了樂(lè)觀鎖,我們知道樂(lè)觀鎖就是對(duì)資源進(jìn)行保護(hù)的,所以答案是不會(huì)扣減兩次,但是不能就此止步,需要結(jié)合第一章節(jié)知識(shí)進(jìn)行進(jìn)一步分析:
t2時(shí)刻session1和session2同時(shí)執(zhí)行update操作,由于update會(huì)加排它鎖,所以兩者只能有一個(gè)成功:session1成功,session2阻塞等待排它鎖釋放。
t3時(shí)刻session1提交事務(wù)釋放排它鎖,此時(shí)session2獲取到鎖進(jìn)行當(dāng)前讀,但是此時(shí)id=1記錄version值已經(jīng)變成了2,執(zhí)行語(yǔ)句已經(jīng)查詢不到待更新數(shù)據(jù),所以沒(méi)有記錄發(fā)生更新。
3 扣減庫(kù)存原理
如果理解了第二章節(jié)樂(lè)觀鎖原理,那么扣減庫(kù)存原理已經(jīng)顯而易見(jiàn),我們假設(shè)商品只剩下1件庫(kù)存,如果兩個(gè)線程同時(shí)執(zhí)行扣減庫(kù)存,會(huì)發(fā)生超賣(mài)的情況嗎?
t2時(shí)刻session1和session2同時(shí)執(zhí)行updatek扣減庫(kù)存,由于update會(huì)加排它鎖,所以兩者只能有一個(gè)成功:session1成功,session2阻塞等待排它鎖釋放。
t3時(shí)刻session1提交事務(wù)釋放排它鎖,此時(shí)session2獲取到鎖進(jìn)行當(dāng)前讀,但是此時(shí)商品1庫(kù)存已經(jīng)變?yōu)?,已經(jīng)不滿足(where stock - 1 >= 0)條件,執(zhí)行語(yǔ)句已經(jīng)查詢不到待更新數(shù)據(jù),所以沒(méi)有記錄發(fā)生更新。
以上就是圖解MySQL中樂(lè)觀鎖扣減庫(kù)存原理的詳細(xì)內(nèi)容,更多關(guān)于MySQL樂(lè)觀鎖扣減庫(kù)存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mysql臨時(shí)表(temporary?table)使用方法詳解
MySQL臨時(shí)表在很多場(chǎng)景中都會(huì)用到,MySQL內(nèi)部在執(zhí)行復(fù)雜SQL時(shí),需要借助臨時(shí)表進(jìn)行分組、排序、去重等操作,下面這篇文章主要給大家介紹了關(guān)于mysql臨時(shí)表(temporary?table)使用方法的相關(guān)資料,需要的朋友可以參考下2024-01-01MySQL?中的count(*)?與?count(1)?誰(shuí)更快一些?
這篇文章主要討論MySQL?中?count(*)?與?count(1)?誰(shuí)更快一些?以下討論基于?InnoDB?存儲(chǔ)引擎,并且再文末單獨(dú)說(shuō)一下MyISAM?,感興趣的小伙伴可以參考一下2022-02-02MySQL中的數(shù)據(jù)備份與SQL語(yǔ)句
這篇文章主要介紹了MySQL中的數(shù)據(jù)備份與SQL語(yǔ)句,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05mysql將數(shù)據(jù)庫(kù)中所有表結(jié)構(gòu)和數(shù)據(jù)導(dǎo)入到另一個(gè)庫(kù)的方法(親測(cè)有效)
這篇文章主要給大家介紹了關(guān)于mysql將數(shù)據(jù)庫(kù)中所有表結(jié)構(gòu)和數(shù)據(jù)導(dǎo)入到另一個(gè)庫(kù)的方法,文中通過(guò)代碼介紹的非常詳細(xì),親測(cè)有效,對(duì)同樣遇到這個(gè)需求的朋友具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01解決Mysql建表時(shí)報(bào)錯(cuò)invalid?ON?UPDATE?clause?for?'create_d
這篇文章主要介紹了解決Mysql建表時(shí)報(bào)錯(cuò)invalid?ON?UPDATE?clause?for?'create_date'?column問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12關(guān)于MySql數(shù)據(jù)庫(kù)Update批量更新不同值的實(shí)現(xiàn)方法
這篇文章主要介紹了關(guān)于MySql數(shù)據(jù)庫(kù)Update批量更新不同值的實(shí)現(xiàn)方法,數(shù)據(jù)庫(kù)管理系統(tǒng)可以通過(guò)SQL管理數(shù)據(jù)庫(kù),定義和操作數(shù)據(jù),維護(hù)數(shù)據(jù)的完整性和安全性,需要的朋友可以參考下2023-05-05