MySQL鎖系列文章已經(jīng)鴿了挺久了,最近趕緊擠了擠時(shí)間,和大家聊一聊MySQL的鎖。
只要學(xué)計(jì)算機(jī),「鎖
」永遠(yuǎn)是一個(gè)繞不過的話題。MySQL鎖也是一樣。
一句話解釋MySQL鎖:
MySQL鎖是解決資源競(jìng)爭(zhēng)的一種方案。
短短一句話卻包含了3點(diǎn)值得我們注意的事情:
- 對(duì)什么資源進(jìn)行競(jìng)爭(zhēng)?
- 競(jìng)爭(zhēng)的方式(或者說情形)有哪些?
- 鎖是如何解決競(jìng)爭(zhēng)的?
這篇文章開始帶你循序漸進(jìn)地理解這幾個(gè)問題。
1. 資源的競(jìng)爭(zhēng)方式
MySQL對(duì)資源的操作無非就是 讀 、寫兩種方式,但是由于事務(wù)并發(fā)執(zhí)行的存在,因此對(duì)同一資源的并發(fā)訪問存在3種形式:
- 讀—讀:并發(fā)事務(wù)同時(shí)讀取相同資源。由于讀操作不會(huì)改變資源本身,因此 這種情況下并不存在并發(fā)安全性問題 。
- 讀—寫/寫—讀:一個(gè)事務(wù)對(duì)資源進(jìn)行讀操作,另一個(gè)事務(wù)對(duì)資源進(jìn)行寫操作。
- 寫—寫:并發(fā)事務(wù)同時(shí)對(duì)同一個(gè)資源進(jìn)行寫操作。
2. 讀—寫/寫—讀下的問題
假設(shè)一種情形,一個(gè)事務(wù)先對(duì)某個(gè)資源進(jìn)行讀操作,然后另一個(gè)事務(wù)再對(duì)該資源進(jìn)行寫操作,如果兩個(gè)事務(wù)到此為止,必然不會(huì)導(dǎo)致并發(fā)問題。
可是事務(wù)這種東西,一般情況下就是包含有很多個(gè)子操作啊。
2.1. 幻讀
想象一下啊,假設(shè)事務(wù)T1
和T2
并發(fā)執(zhí)行,T1
先查找了所有name
為「王剛蛋」的用戶信息,此時(shí)發(fā)現(xiàn)擁有這個(gè)硬漢名字的用戶只有一個(gè)。然后T2
插入了一個(gè)同樣叫做「王剛蛋」的用戶的信息,并且提交了。
幻讀
2.2. 不可重復(fù)讀
再來,同樣是T1
和T2
兩個(gè)事務(wù),T1
通過id = 1
查詢到了一條數(shù)據(jù),然后T2
緊接著UPDATE
(DELETE
也可以)了該條記錄,不同的是,T2
緊接著通過COMMIT
提交了事務(wù)。
此時(shí),T1
再次執(zhí)行相同的查詢操作,會(huì)發(fā)現(xiàn)數(shù)據(jù)發(fā)生了變化,name
字段由「王剛蛋」變成了「蟬沐風(fēng)」。
如果一個(gè)事務(wù)讀到了另一個(gè)已提交事務(wù)修改過的(或者是刪除的)數(shù)據(jù),而導(dǎo)致了前后兩次讀取的數(shù)據(jù)不一致的情況,這種事務(wù)并發(fā)問題叫做 不可重復(fù)讀 。
2.3. 臟讀
事情還沒結(jié)束,假設(shè)T1
和T2
都要訪問user_innodb
表中id
為1
的數(shù)據(jù),不同的是T1
先讀取數(shù)據(jù),緊接著T2
修改了數(shù)據(jù)的name
字段,需要注意的是,T2
并沒有提交!
此時(shí),T1
再次執(zhí)行相同的查詢操作,會(huì)發(fā)現(xiàn)數(shù)據(jù)發(fā)生了變化,name
字段由「王剛蛋」變成了「蟬沐風(fēng)」。
如果一個(gè)事務(wù)讀到了另一個(gè)未提交事務(wù)修改過的數(shù)據(jù),而導(dǎo)致了前后兩次讀取的數(shù)據(jù)不一致的情況,這種事務(wù)并發(fā)問題叫做 臟讀 。
2.4. 鎖與MVCC的關(guān)系
總結(jié)一下:我們?cè)谧x—寫,寫—讀的情況下會(huì)遇到3種讀不一致性的問題,臟讀、不可重復(fù)讀以及幻讀。
那寫—寫呢?很顯然,在不做任何措施的情況下,并發(fā)會(huì)出現(xiàn)更大的問題。那該怎么辦呢?
一切的并發(fā)問題都可以通過串行化解決,但是串行化效率太低了!
再優(yōu)化一下,一切并發(fā)問題都可以通過加鎖來解決,這種方案我們稱為 基于鎖的并發(fā)控制 ( Lock Bases Concurrency Control , LBCC )!但是在讀多寫少的環(huán)境下,客戶端連讀取幾條記錄都需要排隊(duì),效率還是太低了!
因此,MySQL的設(shè)計(jì)者為事務(wù)之間的隔離性提供了不同的級(jí)別,使得開發(fā)者可以根據(jù)自己的業(yè)務(wù)場(chǎng)景設(shè)置不同的隔離級(jí)別,來解決(或者部分解決)讀—寫/寫—讀下的讀一致性問題,而不是一上來就加鎖。
這種機(jī)制叫做MVCC
,如果你對(duì)這個(gè)概念不是很了解,我建議你暫停一下,讀一下我的事務(wù)的隔離性與MVCC這篇文章,寫得賊好!?。ㄗ再u自夸一下)
那有了MVCC是不是在讀—寫/寫—讀的情況下就不需要鎖了呢?那也不是。
MVCC解決的是讀—寫/寫—讀中“ 比較純粹的讀 ”遇到的一致性問題,原諒我,這是我自己編的詞兒。那什么是不純粹的?拿存款業(yè)務(wù)舉個(gè)例子。
假設(shè)陀螺要存一筆錢,系統(tǒng)需要先把陀螺的余額讀出來,然后在余額的基礎(chǔ)上加上本次存款的金額,最后再寫入到數(shù)據(jù)庫(kù)中。在將余額讀出來之后,如果不想讓其他事務(wù)繼續(xù)訪問該余額,直到整個(gè)存款事務(wù)完成之后,其他事務(wù)才可以對(duì)該余額繼續(xù)進(jìn)行操作,這種情況下就必須為余額的讀取操作添加鎖。
再總結(jié)一下: MVCC是MySQL默認(rèn)的解決讀—寫/寫—讀下一致性問題的方式,不需要加鎖。而鎖是實(shí)現(xiàn)一致性的最終兜底方案,在某些特殊場(chǎng)景下,鎖的使用不可避免 。
說得更準(zhǔn)確一點(diǎn),MVCC是MySQL在
READ COMMITTED
、REPEATABLE READ
這兩種隔離級(jí)別之下執(zhí)行普通SELECT
操作時(shí)默認(rèn)解決一致性問題的方式。具體為什么只是這兩種隔離級(jí)別,建議你看看事務(wù)的隔離性與MVCC。
2.5. 鎖與事務(wù)的關(guān)系
事務(wù)是多個(gè)操作的集合,比如我們可以把「把大象裝冰箱」這件事情作為一個(gè)事務(wù)。
事務(wù)有A
(原子性)、C
(一致性)、I
(隔離性)、D
(持久性)4大特性,而鎖就是實(shí)現(xiàn)隔離性的其中一種方案(比如還有MVCC等方案)。
事務(wù)的隔離性針對(duì)不同場(chǎng)景需求又實(shí)現(xiàn)了不同的隔離級(jí)別,不同的隔離級(jí)別下,事務(wù)使用鎖的方式又會(huì)有所不同。舉個(gè)例子。
在READ COMMITTED
、REPEATABLE READ
這兩種隔離級(jí)別之下,SELECT
操作是不需要加鎖的,直接使用MVCC機(jī)制即可滿足當(dāng)前隔離級(jí)別的需求。但是在SERIALIZABLE
隔離級(jí)別,并且在禁用自動(dòng)提交時(shí)(autocommit=0),MySQL會(huì)將普通的SELECT
語句轉(zhuǎn)化為SELECT ... LOCK IN SHARE MODE
這樣的加鎖語句,如果你看不懂這句話也沒關(guān)系,你只需要知道MySQL自動(dòng)加鎖了就行,更詳細(xì)的下文再說。
另外,一個(gè)事務(wù)可能會(huì)加很多個(gè)鎖,但是某個(gè)鎖一定只屬于一個(gè)事務(wù)。這就好比一個(gè)管理員可以管理多個(gè)保險(xiǎn)柜,一個(gè)保險(xiǎn)柜一定只被一個(gè)管理員管理。
3. 寫—寫情況
寫—寫的情況下肯定要加鎖的了,所以接下來終于要聊一聊鎖了。
我們首先研究一下鎖住的東西的大小,也就是鎖的粒度。
4. 鎖的粒度
舉一個(gè)非常應(yīng)景的例子。疫情防控的時(shí)候,是封鎖整個(gè)小區(qū)還是封鎖某棟樓的某個(gè)單元,這完全是兩種概念。
對(duì)應(yīng)到MySQL鎖的粒度,那就是表鎖
和行鎖
。
很容易想到,封鎖小區(qū)的行為遠(yuǎn)比封鎖某棟樓某單元的行為粗曠,因此,
從鎖定粒度上來看,表鎖 > 行鎖
直接堵住小區(qū)的門口要比進(jìn)入小區(qū)找到具體某棟樓的某個(gè)單元要快不少,因此,
從加鎖效率上來看,表鎖 > 行鎖
直接鎖住小區(qū)大概率會(huì)影響其他樓居民的正常生活和各種社會(huì)活動(dòng)的開展,而鎖住某棟樓某單元頂多影響這一個(gè)單元的居民的生活,因此,
從沖突概率來看,表鎖 > 行鎖
從并發(fā)性能來看,表鎖 < 行鎖
MySQL支持很多存儲(chǔ)引擎,而不同的存儲(chǔ)引擎對(duì)鎖的支持也不盡相同。對(duì)于MyISAM
、MERGE
、MEMORY
這些存儲(chǔ)引擎而言,只支持表鎖;而InnoDB
存儲(chǔ)引擎既支持表鎖也支持行鎖,下文討論的所有內(nèi)容均針對(duì)InnoDB存儲(chǔ)引擎。
說完鎖的粒度,還有一件事情需要我們仔細(xì)考慮一下。上文說過,READ COMMITTED
、REPEATABLE READ
這兩種隔離級(jí)別之下,SELECT
操作默認(rèn)采用MVCC機(jī)制就可以了,壓根兒不需要加鎖,那么問題來了,萬一我就是想加鎖呢?
你可能會(huì)說,“簡(jiǎn)單啊,那就加鎖!把數(shù)據(jù)鎖死!除了我誰也別動(dòng)!”
很好,但是對(duì)于大部分讀—讀而言,由于不會(huì)出現(xiàn)讀一致性問題,所以不讓其他事務(wù)進(jìn)行讀操作并不合理。
你可能又說,“那行吧,那就讓讀操作加鎖的時(shí)候允許其他事務(wù)對(duì)鎖住的數(shù)據(jù)進(jìn)行讀操作,但是不允許寫操作?!?/p>
嗯,想得確實(shí)更細(xì)致了一些。但是再想想我上文中舉過的陀螺存錢的例子,有時(shí)候SELECT
操作需要獨(dú)占數(shù)據(jù),其他事務(wù)既不能讀,更不能寫。
我們把這種共享和排他的性質(zhì)稱為鎖的基本模式。
5. 鎖的基本模式
5.1. 共享鎖
共享鎖(Shared Lock),簡(jiǎn)稱S
鎖,可以同時(shí)被多個(gè)事務(wù)共享,也就是說,如果一個(gè)事務(wù)給某個(gè)數(shù)據(jù)資源添加了S
鎖,其他事務(wù)也被允許獲取該數(shù)據(jù)資源的S
鎖。
由于S
鎖通常被用于讀取數(shù)據(jù),因此也被稱為 讀鎖 。
那怎么給數(shù)據(jù)添加S
鎖呢?
我們可以用 SELECT ... LOCK IN SHARE MODE;
的方式,在讀取數(shù)據(jù)之前就為數(shù)據(jù)添加一把S
鎖。如果當(dāng)前事務(wù)執(zhí)行了該語句,那么會(huì)為讀取到的記錄添加S
鎖,同時(shí)其他事務(wù)也可以使用SELECT ... LOCK IN SHARE MODE;
方式繼續(xù)獲取這些數(shù)據(jù)的S
鎖。
我們通過以下的例子驗(yàn)證一下S
鎖是否可以重復(fù)獲取。
5.2. 排他鎖
排他鎖(Exclusive Lock),簡(jiǎn)稱X
鎖。只要一個(gè)事務(wù)獲取了某數(shù)據(jù)資源的X
鎖,其他的事務(wù)就不能再獲取該數(shù)據(jù)的X
鎖和S
鎖。
由于X
鎖通常被用于修改數(shù)據(jù),因此也被稱為 寫鎖 。
X
鎖的添加方式有兩種,
- 自動(dòng)添加
X
鎖
我們對(duì)記錄進(jìn)行增刪改時(shí),通常情況下會(huì)自動(dòng)對(duì)其添加X
鎖。 - 手動(dòng)加鎖
我們可以用SELECT ... FOR UPDATE;
的方式,在讀取數(shù)據(jù)之前就為數(shù)據(jù)添加一把X
鎖。如果當(dāng)前事務(wù)執(zhí)行了該語句,那么會(huì)為讀取到的記錄添加X
鎖,這樣既不允許其他事務(wù)獲取這些記錄的S
鎖,也不允許獲取這些記錄的X
鎖。
我們用下面的例子驗(yàn)證一下X
鎖的排他性。
通常情況下,事務(wù)提交或結(jié)束事務(wù)時(shí),鎖會(huì)被釋放。
-
計(jì)算機(jī)
+關(guān)注
關(guān)注
19文章
7611瀏覽量
89881 -
MySQL
+關(guān)注
關(guān)注
1文章
841瀏覽量
27369 -
MVCC
+關(guān)注
關(guān)注
0文章
13瀏覽量
1530
發(fā)布評(píng)論請(qǐng)先 登錄
基于MySQL的鎖機(jī)制
redis分布式鎖場(chǎng)景實(shí)現(xiàn)
請(qǐng)問DSPflash鎖死后,RAM還可以用嗎,還是這塊芯片就徹底廢了?
360為什么會(huì)入局智能鎖市場(chǎng)
詳細(xì)介紹MySQL InnoDB存儲(chǔ)引擎各種不同類型的鎖

評(píng)論