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