本文主要是帶大家快速了解 InnoDB 中鎖相關的知識
為什么需要加鎖
首先,為什么要加鎖?我想我不用多說了,想象接下來的場景你就能 GET 了。
你在商場的衛生間上廁所,此時你一定會做的操作是啥?鎖門。如果不鎖門,上廁所上著上著,啪一下門就被打開了,可能大概也許似乎貌似有那么一丁點的不太合適。
數據也是一樣,在并發的場景下,如果不對數據加鎖,會直接破壞數據的一致性,并且如果你的業務涉及到錢,那后果就更嚴重了。
鎖門表情包
鎖的分類
在 InnoDB 中,都有哪些鎖?其實你應該已經知道了很多了,例如面試中會問你存儲引擎 MyISAM 和 InnoDB 的區別,你會說 MyIASM 只有表鎖,但是 InnoDB 同時支持行鎖和表鎖。你可能還會被問到樂觀鎖和悲觀鎖的區別是啥。
鎖的概念、名詞很多,如果你沒有對鎖構建出一個完整的世界觀,那么你理解起來就會比較有阻礙,接下來我們把這些鎖給分一下類。
按照鎖的粒度
按照鎖的粒度進行劃分可以分為:
- 表鎖
- 行鎖
這里就不討論頁鎖了,頁鎖是 BDB(BerkeleyDB) 存儲引擎中才有的概念,我們這里主要討論 InnoDB 存儲引擎。
按照鎖的思想
按照加鎖的思想可以分為:
- 悲觀鎖
- 樂觀鎖
這里的悲觀、樂觀和你平時理解的名詞是同一個意思。樂觀鎖認為大概率不會發生沖突,只在必要的時候加鎖。而悲觀鎖認為大概率會沖突,所以無論是否必要加鎖都會執行加鎖操作。
按照兼容性
按照兼容性可以把鎖劃分為:
- 共享鎖
- 排他鎖
被加上共享鎖的資源,能夠和其他人進行共享,而如果被加上了排他鎖,其他人在拿不到這把鎖的情況下是無法進行任何操作的。
按照鎖的實現
這里的實現就是 InnoDB 中具體的鎖的種類了,分別有:
- 意向鎖(Intention Locks)
- 記錄鎖(Record Locks)
- 間隙鎖(Gap Locks)
- 臨鍵鎖(Next-Key Locks)
- 插入意向鎖(Insert Intention Locks)
- 自增鎖(AUTO-INC Locks)
即使按照這種分類來對鎖進行了劃分,看到了這么多的鎖的名詞可能仍然會有點懵。比如我SELECT ... FOR UPDATE 的時候到底加的是什么鎖?
我們應該透過現象看本質,本質是什么?本質是鎖到底加在了什么對象上,而這個很好回答:
- 加在了表上
- 加在了行上
而對于加在行上的鎖,其本質又是什么?本質是將鎖加在了索引上。
意向鎖
在 InnoDB 中支持了不同粒度的鎖,行鎖和表鎖。例如lock tables命令就會持有對應表的排他鎖。為了使多種不同粒度的鎖更實用,InnoDB 設計了意向鎖。
意向鎖是一種表級鎖,它表明了接下來的事務中,會使用哪種類型的鎖,它有以下兩種類型:
- 共享意向鎖(IS) 表明該事務會打算對表中的記錄加共享鎖
- 獨占意向鎖(IX) 則是加排他鎖
例如,select ... for share就是加的共享意向鎖,而SELECT .. FOR UPDATE則是加的獨占意向鎖。其規則如下:
- 一個事務如果想要獲取某張表中某行的共享鎖,它必須先獲取該表的共享意向鎖,或者獨占意向鎖。
- 同理,如果想獲取排他鎖,它必須先獲取獨占意向鎖
下圖是這幾種鎖的組合下相互互斥、兼容的情況
對照上面的表,在相互兼容的情況下,對應的事務就能獲取鎖,但是如果不兼容則無法獲取鎖,直到不兼容的鎖釋放之后才能獲取。
看到這里你可能就會有問題了,那既然意向鎖除了 LOCK TBALES 之外什么都不阻塞。那我要它何用?
還是通過例子,假設事務 A 獲取了 student 表中 id = 100 這行的共享鎖,之后事務 B 需要申請 student 表的排他鎖。而這兩把鎖明顯是沖突的,而且還是對于同一行。
那 InnoDB 需要如何感知 A 獲取了這把鎖?遍歷整個 B+ 樹嗎?不,答案就是意向鎖。事務 B 申請寫表的排他鎖時,InnoDB 會發現事務 A 已經獲取了該表的意向共享鎖,說明 student 表中已經有記錄被共享鎖鎖住了。此時就會阻塞住。
并且,意向鎖除了像LOCK TABLES這種操作之外,不會阻塞其他任何操作。換句話說,意向鎖只會和表級別的鎖之間發生沖突,而不會和行級鎖發生沖突。因為意向鎖的主要目的是為了表明有人即將、或者正在鎖定某一行。
就像你去圖書館找書,你并不需要每個書架挨著挨著找,直接去服務臺用電腦一搜,就知道圖書館有沒有這本書。
記錄鎖
這就是記錄鎖,是行鎖的一種。記錄鎖的鎖定對象是對應那行數據所對應的索引。對索引不太清楚的可以看看這篇文章。
當我們執行SELECT * FROM student WHERE id = 1 FOR UPDATE語句時,就會對值為1的索引加上記錄鎖。至于要是一張表里沒有索引該怎么辦?這個問題在上面提到的文章中也解釋過了,當一張表沒有定義主鍵時,InnoDB 會創建一個隱藏的RowID,并以此 RowID 來創建聚簇索引。后續的記錄鎖也會加到這個隱藏的聚簇索引上。
當我們開啟一個事務去更新 id = 1 這行數據時,如果我們不馬上提交事務,然后再啟一個事務去更新 id = 1 的行,此時使用 show engine innodb status查看,我們可以看到lock_mode X locks rec but not gap waiting的字樣。
X是排他鎖的意思,從這可以看出來,記錄鎖其實也可以分為共享鎖、排他鎖模式。當我們使用FOR UPDATE是排他,而使用LOCK IN SHARE MODE 則是共享。
而在上面字樣中出現的 gap 就是另一種行鎖的實現間隙鎖。
間隙鎖
對于間隙鎖(Gap Locks)而言,其鎖定的對象也是索引。為了更好的了解間隙鎖,我們舉個例子。
- SELECT name FROM student WHERE age BETWEEN 18 AND 25 FOR UPDATE
假設我們為 age 建立了非聚簇索引,運行該語句會阻止其他事務向 student 表中新增 18-25 的數據,無論表中是否真的有 age 為 18-25 的數據。因為間隙鎖的本質是鎖住了索引上的一個范圍,而 InnoDB 中索引在底層的B+樹上的存儲是有序的。
再舉個例子:
- SELECT * FROM student WHERE age = 10 FOR UPDATE;
值得注意的是,這里的 age 不是唯一索引,就是一個簡單的非聚簇索引。此時會給 age = 10的數據加上記錄鎖,并且鎖定 age < 10 的 Gap。如果當前這個事務不提交,其他事務如果要插入一條 age < 10 的數據時,會被阻塞住。
間隙鎖是 MySQL 在對性能、并發綜合考慮之下的一種折中的解決方案,并且只在**可重復讀(RR)下可用,如果當前事務的隔離級別為讀已提交(RC)**時,MySQL會將間隙鎖禁用。
剛剛說了,記錄鎖分為共享、排他,間隙鎖其實也一樣。但是不同于記錄鎖的一點,共享間隙鎖、排他間隙鎖相互不互斥,這是怎么回事?
我們還是需要透過現象看到本質,間隙鎖的目的是什么?
為了防止其他事務在 Gap 中插入數據
那共享、排他間隙鎖在這個目標上是一致的,所以是可以同時存在的。
臨鍵鎖
臨鍵鎖(Next-Key Locks)是 InnoDB 最后一種行鎖的實現,臨鍵鎖實際上是記錄鎖和間隙鎖的組合。換句話說,臨鍵鎖會給對應的索引加上記錄鎖,并且外加鎖定一個區間。
但是并不是所有臨鍵鎖都是這么玩的,對于下面的SQL:
- SELECT * FROM student WHERE id = 23;
在這種情況下,id是主鍵,唯一索引,無論其他事務插入了多少數據,id = 23這條數據永遠也只有一條。此時再加一個間隙鎖就完全沒有必要了,反而會降低并發。所以,在使用的索引是唯一索引的時候,臨鍵鎖會降級為記錄鎖。
假設我們有10,20,30總共3條索引數據。那么對應臨鍵鎖來說,可能鎖定的區間就會如下:
- (∞, 10]
- (10, 20]
- (20, 30]
- (30, ∞)
InnoDB 的默認事務隔離級別為可重復讀(RR),在這個情況下,InnoDB 就會使用臨鍵鎖,以防止幻讀的出現。
簡單解釋一下幻讀,就是在事務內,你執行了兩次查詢,第一次查詢出來 5 條數據,但是第二次再查,居然查出了 7 條數據,這就是幻讀。
可能你在之前的很多博客,或者面試八股文上,了解到過 InnoDB 的RR事務隔離級別可以防止幻讀,RR防止幻讀的關鍵就是臨鍵鎖。
舉個例子,假設 student 表中就兩行數據,id分別為90和110.
- SELECT * FROM student WHERE id > 100 FOR UPDATE;
當執行該 SQL 語句之后,InnoDB就會給區間 (90, 110] 和(110,∞) 加上間隙鎖,同時給 id=110 的索引加上記錄鎖。這樣以來,其他事務就無法向這個區間內新增數據,即使 100 根本不存在。
插入意向鎖
接下來是插入意向鎖(Insert Intention Locks),當我們執行 INSERT 語句之前會加的鎖。本質上是間隙鎖的一種。
還是舉個例子,假設我們現在有索引記錄10、20,事務A、B分別插入索引值為14、16的數據,此時事務A和B都會用插入意向鎖鎖住 10-20 之間的 Gap,獲取了插入意向鎖之后就會獲取14、16的排他鎖。
此時事務A和B是不會相互阻塞的,因為他們插入的是不同的行。
自增鎖
最后是自增鎖(AUTO-INC Locks),自增鎖的本質是表鎖,較為特殊。當事務 A 向包含了 AUTO_INCREMENT 列的表中新增數據時,就會持有自增鎖。而此時其他的事務 B 則必須要等待,以保證事務 A 取得連續的自增值,中間不會有斷層。
原文鏈接:https://mp.weixin.qq.com/s/rB0MHssNG_9ivZP2ka-EYw