大家好,我是公眾號:java小杰要加油歹颓, 今天來分享一個關(guān)于mysql的知識點(diǎn)——mysql中的鎖
- 話不多說,直接開車
事務(wù)并發(fā)訪問情況
讀-讀 情況
- 并發(fā)事務(wù)讀取相同的數(shù)據(jù),并不會對數(shù)據(jù)造成影響童芹,允許并發(fā)讀
寫-寫 情況
-
多事務(wù)并發(fā)寫寫時會發(fā)生臟寫的情況涝滴,不過任何一個事務(wù)隔離級別都不允許此情況發(fā)生绣版,通過加鎖來杜絕臟寫
臟寫
- 事務(wù)T1 將數(shù)據(jù)改成了A,但是還未提交,可此時事務(wù)T2又將數(shù)據(jù)改成了B歼疮,覆蓋了事務(wù)T1的更改杂抽,T1更新丟失,這種情況叫做臟寫
加鎖
-
例如韩脏,現(xiàn)在事務(wù)T1,T2對這條記錄進(jìn)行并發(fā)更改缩麸,剛才說是隔離級別是通過加鎖來杜絕此臟寫的,流程如下 image
這個鎖結(jié)構(gòu)中有兩個比較關(guān)鍵的信息(其實(shí)還有很多信息赡矢,后面再聊)
trx信息:表示這個鎖結(jié)構(gòu)是和哪個事務(wù)所關(guān)聯(lián)的
is_waiting信息:表示當(dāng)前事務(wù)是否正在等待
Q: 能描述一下兩個事務(wù)并發(fā)修改同一條數(shù)據(jù)時杭朱,mysql這個鎖是怎么避免臟寫的嗎?
A :事務(wù)T1在更改這條數(shù)據(jù)前吹散,就先內(nèi)存中生成一把鎖與此數(shù)據(jù)相關(guān)聯(lián)(is_waiting為false,代表沒有等待)痕檬,然后咔咔一頓操作更改數(shù)據(jù),這個時候送浊,事務(wù)T2來了梦谜,發(fā)現(xiàn)此記錄已經(jīng)有一把鎖與之相關(guān)聯(lián)了(就是T1那一把鎖),然后就開始等待(is_waiting為true代表正在等待)袭景,事務(wù)T1更改完數(shù)據(jù)提交事務(wù)后唁桩,就會把此事務(wù)對應(yīng)的所結(jié)構(gòu)釋放掉,然后檢測一下還有沒有與此記錄相關(guān)聯(lián)的鎖耸棒,結(jié)果發(fā)現(xiàn)T2還在苦苦的等待荒澡,就把T2的鎖結(jié)構(gòu)的(is_waiting為false,代表沒有等待)然后把T2事務(wù)對應(yīng)的線程喚醒,T2獲取鎖成功繼續(xù)執(zhí)行与殃,總體流程如上单山。
讀-寫 /寫-讀 情況
在讀-寫 / 寫 -讀的情況下會出現(xiàn)臟讀碍现,不可重復(fù)讀,幻讀的現(xiàn)象米奸,不同的隔離級別可以避免不同的問題昼接,具體相關(guān)內(nèi)容可以看小杰的這篇文章 京東面試官問我:“聊聊MySql事務(wù),MVCC?”
不過貼心的我還是列出來了 注:√代表可能發(fā)生悴晰,×代表不可能發(fā)生
隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|
讀未提交(read uncommitted RU) | √ | √ | √ |
讀提交(read committed RC) | × | √ | √ |
可重復(fù)讀(repeatable read RR) | × | × | √ |
串行化(serializable ) | × | × | × |
但是 RR在某些程度上避免了幻讀的發(fā)生
怎么避免臟讀慢睡、不可重復(fù)讀、幻讀這些現(xiàn)象呢铡溪?其實(shí)有兩種方案
- 方案一 :讀操作使用MVCC漂辐,寫操作進(jìn)行加鎖
- mvcc里面最重要的莫過于ReadView了,它的存在保證了事務(wù)不可以讀取到未提交的事務(wù)所作的更改棕硫,避免了臟讀髓涯。
- 在RC隔離級別下,每次select讀操作都會生成ReadView
- 在RR隔離級別下哈扮,只有第一次select讀操作才會生成ReadView复凳,之后的select讀操作都復(fù)用這一個ReadView
- 方案二:讀寫操作都用加鎖
某些業(yè)務(wù)場景不允許讀取舊記錄的值,每次讀取都要讀取最新的值灶泵。 例如銀行取款事務(wù)中育八,先把余額讀取出來,再對余額進(jìn)行操作赦邻。當(dāng)這個事務(wù)在讀取余額時髓棋,不允許其他事務(wù)對此余額進(jìn)行訪問讀取,直到取款事務(wù)結(jié)束后才可以訪問余額惶洲。所以在讀數(shù)據(jù)的時候也要加鎖
鎖分類
當(dāng)使用讀寫都加鎖這個方案來避免并發(fā)事務(wù)寫-寫按声、讀-寫、寫-讀時而產(chǎn)生的臟讀恬吕,不可重復(fù)讀签则,幻讀現(xiàn)象時,那么這個鎖它就要做到铐料,讀讀時不相互影響渐裂,上面三種情況時要相互阻塞,這時鎖也分了好幾類钠惩,我們繼續(xù)往下看
鎖定讀
- 共享鎖(Shared Lock):簡稱S鎖柒凉,在事務(wù)要讀取一條記錄時,需要先獲取該記錄的S鎖
- 獨(dú)占鎖(Exclusive Lock):簡稱X鎖篓跛,也稱排他鎖膝捞,在事務(wù)要改動一條記錄時,需要先獲取該記錄的X鎖
他們之間兼容關(guān)系如下 √代表可以兼容愧沟,×代表不可兼容
兼容性 | S鎖 | X鎖 |
---|---|---|
S鎖 | √ | × |
X鎖 | × | × |
事務(wù)T1獲取某記錄的S鎖后蔬咬,
- 事務(wù)T2也可以獲取此記錄的S鎖鲤遥,(兼容)
- 事務(wù)T2不可以獲取此記錄的X鎖,直到T1提交后將S鎖釋放 (不兼容)
事務(wù)T1獲取某記錄的X鎖后林艘,
- 事務(wù)T2不可以獲取此記錄的S鎖盖奈,直到T1提交后將X鎖釋放 (不兼容)
- 事務(wù)T2不可以獲取此記錄的X鎖,直到T1提交后將X鎖釋放 (不兼容)
鎖定讀語句
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`SELECT .. LOCK IN SHARE MODE # 對讀取的記錄添加S鎖
SELECT .. FOR UPDATE # 對讀取的記錄添加X鎖` </pre>
多粒度鎖
前面提到的鎖都是針對記錄的北启,其實(shí)一個事務(wù)也可以在表級進(jìn)行加鎖(S鎖卜朗、X鎖)
-
T1給表加了S鎖拔第,那么
- T2可以繼續(xù)獲取此表的S鎖
- T2可以繼續(xù)獲取此表中的某些記錄的S鎖
- T2不可以繼續(xù)獲取此表的X鎖
- T2不可以繼續(xù)獲取此表中的某些記錄的X鎖
-
T1給表加了X鎖咕村,那么
- T2不可以繼續(xù)獲取此表的S鎖
- T2不可以繼續(xù)獲取此表中的某些記錄的S鎖
- T2不可以繼續(xù)獲取此表的X鎖
- T2不可以繼續(xù)獲取此表中的某些記錄的X鎖
可是怎么可能平白無故的就給表加鎖呢,難道沒什么條件嗎蚊俺?答案是肯定有條件的
- 若想給表加S鎖懈涛,得先確保表中記錄沒有X鎖
- 若想給表加X鎖,得先確保表中記錄沒有X鎖和S鎖
但是這個怎么確保呢泳猬?難道要一行一行的遍歷表中的所有數(shù)據(jù)嗎批钠?當(dāng)然不是啦,聰明的大佬們想出了下面這兩把鎖
- 意向共享鎖(Intention Shared Lock):簡稱IS鎖得封,當(dāng)事務(wù)準(zhǔn)備在某記錄上加S鎖時埋心,需要先在表級別加上一個IS鎖
- 意向獨(dú)占鎖(Intention Exclusive Lock):簡稱IX鎖,當(dāng)事務(wù)準(zhǔn)備在某記錄上加X鎖時忙上,需要先在表級別加上一個IX鎖
讓我們來看下加上這兩把鎖之后的效果是什么樣子的
- 當(dāng)想給記錄加S鎖時拷呆,先給表加一個IS鎖,然后再給記錄加S鎖
- 當(dāng)想給記錄加X鎖時疫粥,先給表加IX鎖茬斧,然后再給記錄加X鎖
然后 經(jīng)過上面的操作之后
- 如果想給表加S鎖,先看下表加沒加IX鎖梗逮,如果有的話项秉,則表明此表中的記錄有X鎖,則需要等到IX鎖釋放掉后才可以加S鎖
- 如果想給表加X鎖慷彤,先看下表加沒加IS鎖或者IX鎖娄蔼,如果有的話,則表明此表中的記錄有S鎖或者X鎖底哗,則需要等到IS鎖或者IX鎖釋放掉后才可以加X鎖
這幾種鎖的兼容性如下表
兼容性 | IS鎖(表級鎖) | S鎖 | IX鎖(表級鎖) | X鎖 |
---|---|---|---|---|
IS鎖(表級鎖) | √ | √ | √ | × |
S鎖 | √ | √ | × | × |
IX鎖(表級鎖) | √ | × | √ | × |
X鎖 | × | × | × | × |
- IS贷屎、IX鎖都是表級鎖,他們可以共存艘虎。
- 他們的提出僅僅是為了在之后加表級別的S鎖或者X鎖時可以快速判斷表中的記錄是否被上鎖唉侄,避免用遍歷的方式來查看一行一行的去查看而已
InnoDB中的行級鎖
Record Lock(記錄鎖)
- 官方名字 LOCK_REC_NOT_GAP
- 僅僅鎖住一條記錄
- 有S型和X型之分
Gap Lock(間隙鎖)
- 官方名字 LOCK_GAP
- 給某記錄加此鎖后,阻塞數(shù)據(jù)在此記錄和上一個記錄的間隙插入野建,但是不鎖定此記錄
- 有S型和X型之分属划,可是并沒有什么區(qū)別他們的作用是相同的恬叹,gap鎖的作用僅僅是為了防止插入幻影記錄而已,如果對一條記錄加了gap鎖(無論S/X型)并不會限制其他事務(wù)對這條記錄加Record Lock或者Gap Lock
Next-Key Lock(記錄鎖+間隙鎖)
- 官方名字 LOCK_ORDINARY
- 既可以鎖住某條記錄同眯,又可以組織其他事務(wù)在該記錄面前插入新記錄
Insert Intention Lock(插入意向鎖鎖)
- 官方名字 LOCK_INSERT_INTENTION
- 事務(wù)在插入記錄時绽昼,如果插入的地方加了gap鎖,那么此事務(wù)需要等待须蜗,此時此事務(wù)在等待時也需要生成一個鎖結(jié)構(gòu)硅确,就是插入意向鎖
鎖內(nèi)存結(jié)構(gòu)
- 我們難道鎖一條記錄就要生成一個鎖結(jié)構(gòu)嗎?
當(dāng)然不是明肮!
一個鎖結(jié)構(gòu)
如果被加鎖的記錄符合下面四條狀態(tài)的話菱农,那么這些記錄的鎖則會合到一個鎖結(jié)構(gòu)中
- 在同一個事務(wù)中進(jìn)行加鎖操作
- 被加鎖的記錄在同一個頁面中
- 加鎖的類型是一樣的
- 等待的狀態(tài)是一樣的
鎖結(jié)構(gòu)信息
然后我們再來依此看下這個所結(jié)構(gòu)每個部分的信息都是什么意思
鎖所在的事務(wù)信息:無論是表級鎖還是行級鎖,一個鎖屬于一個事務(wù)柿估,這里記載著該鎖對應(yīng)的信息
索引信息:對于行級鎖來說循未,需要記錄一下加鎖的記錄屬于哪個索引
-
表鎖/行鎖信息:行級鎖
- Space_ID:記錄所在的表空間 *** Page Number**:記錄所在的頁號
-
n_bits:一條記錄對應(yīng)著一個比特;一個頁面包含多條記錄秫舌,用不同的比特來區(qū)分到底是那一條記錄加了鎖,有個計算公式如下(公式中是取商)
n_bits = (1+(n_recs+LOCK_PAGE_BITMAP_MARGIN)/ 8)x 8
LOCK_PAGE_BITMAP_MARGIN是固定的值為64的妖,n_recs指當(dāng)前界面一共有多少條記錄(包含偽記錄以及在垃圾鏈表中的記錄),
type_mode:32比特的數(shù)
-
lock_mode(鎖模式):低4比特位表示
- LOCK_AUTO_INC(十進(jìn)制的4):表示AUTO-INC鎖
- LOCK_IS(十進(jìn)制的0):表示共享意向鎖足陨,IS鎖
- LOCK_IX(十進(jìn)制的1):表示獨(dú)占意向鎖嫂粟,IX鎖
- LOCK_S(十進(jìn)制的2):表示共享鎖,也就是S鎖
- LOCK_X(十進(jìn)制的3):表示獨(dú)占鎖墨缘,也就是X鎖
-
lock_type(鎖類型):第5~8比特位表示
- LOCK_TABLE(十進(jìn)制的1):當(dāng)?shù)?比特位設(shè)置為1時星虹,表示表級鎖
- LOCK_REC(十進(jìn)制的32):當(dāng)?shù)?比特位設(shè)置為1時,表示行級鎖
-
rec_lock_type(行鎖的具體類型):其余的比特位表示
LOCK_ORDINARY(十進(jìn)制的0):表示next-key鎖
LOCK_GAP(十進(jìn)制的512):當(dāng)?shù)?0比特位是1時飒房,表示gap鎖
LOCK_REC_NOT_GAP(十進(jìn)制的1024):也就是當(dāng)?shù)?1比特設(shè)置為1時搁凸,表示Record Lock(記錄鎖)
LOCK_INSERT_INTENTION(十進(jìn)制的2048):也就是當(dāng)?shù)?2比特設(shè)置為1時,表示Insert Intention Lock(插入意向鎖)
-
LOCK_WAIT(十進(jìn)制的256):也就是當(dāng)
- 第9比特設(shè)置為1時狠毯,表示is_waiting為true,即當(dāng)前事務(wù)獲取鎖失敗护糖,處于等待狀態(tài)
- 第9比特設(shè)置為0時,表示is_waiting為false,即當(dāng)前事務(wù)獲取鎖成功
其他信息:此文章不討論
一堆比特位:此文章不討論
舉個例子
事務(wù)T1 要給user表中的記錄加鎖嚼松,假設(shè)這些記錄存儲在表空間號為20嫡良,頁號為21的頁面上,T1給id=1的記錄加S型Record Lock鎖献酗,假如當(dāng)前頁面一共有5條記錄(3條用戶記錄和2條偽記錄)
過程:先給表加IS鎖寝受,不過我們現(xiàn)在不關(guān)心,只關(guān)心行級鎖罕偎, 具體生成的所結(jié)構(gòu)如下圖所示
最后
- 快過年啦很澄,小杰可能也需要休息一下下,因?yàn)樽罱贾芨m然上周有點(diǎn)事沒更,打臉)甩苛,周末完全沒有其余時間了
- 感覺和朋友家人們聯(lián)系有點(diǎn)少了蹂楣,過年回家鞏固下感情和朋友們聊聊天吹吹牛逼,順便維護(hù)下峽谷的治安
- 最后祝關(guān)注java小杰要加油的寶貝兒們
- 脫單暴富事事順讯蒲,升職加薪牛哄哄痊土!
好文推薦
- 五千來字小作文,是的墨林,我們是有個HTTP赁酝。
- 同學(xué),二叉樹的各種遍歷方式旭等,我都幫你總結(jié)了酌呆,附有隊列堆棧圖解(鞏固基礎(chǔ),強(qiáng)烈建議收藏)
- 京東面試官問我:“聊聊MySql事務(wù),MVCC辆雾?(好文肪笋,建議收藏)”
- 你好月劈,我叫AQS(系列一:加鎖)
- 京東這道面試題你會嗎度迂?
- ?線程池為什么可以復(fù)用猜揪,我是蒙圈了惭墓。。而姐。