一個MySQL鎖和麪試官大戰三十回合,我霸中霸!

我,小Y。

又來面試了,還是之前那家公司,即將和之前那個老面試官進行第二次 battle,心情還是xue微有點忐忑。

沒看過第一次 battle 的同學可以看這裏,一個MVCC和麪試官大戰三十回合

又一抹光亮閃過,面試官推門而入,我擡頭望去,沒錯,還是那味兒。

看到面試官頭上那“傲然矗立”的頭髮,差點又想站起來給他敬了個禮,算了先穩住,低調一點。

面試官瞥了我一眼:來吧,咱們繼續面試,上次沒辦法,女朋友就是粘人,這次問 MySQL InnoDB 的鎖喔。

我:.....(行,我知道你有女朋友了),好的面試官,您請。

面試官:MySQL InnoDB 的鎖 和 MyISAM 的鎖有什麼區別?

我:MyISAM 只支持表鎖,一鎖就鎖整張表,而 InnoDB 不僅支持表鎖,還支持粒度更低的行鎖,僅對相關的記錄上鎖即可,所以對於寫入操作來說 InnoDB 的性能更高。

面試官:那不論表鎖還是行鎖,其實有分爲兩類的,你知道是哪兩類嗎?

我:你指的是 shared (S) locks 和 exclusive (X) locks 嗎?

S鎖,稱爲共享鎖,事務在讀取記錄的時候獲取 S 鎖,它允許多個事務同時獲取 S 鎖,互相之間不會衝突。

X鎖,稱爲獨佔鎖,事務在修改記錄的時候獲取 X 鎖,且只允許一個事務獲取 X 鎖,其它事務需要阻塞等待。

所以 S 鎖之間不衝突,X 鎖則爲獨佔鎖,所以 X 之間會衝突, X 和 S 也會衝突。

不論是表級別鎖還是行級別鎖,S 和 X 的特性都是一樣的。

面試官:你說事務在讀取記錄的時候需要獲取 S 鎖?這不對吧?

我:確實不準確。益與 MVCC 的功勞,普通的 select 是不需要加鎖的。

而 SELECT ... LOCK IN SHARE MODE; 這種讀取需要對記錄上 S 鎖。

SELECT ... FOR UPDATE; 這種需要對記錄上 X 鎖。

面試官:對了,你剛提到表級鎖,那你平時用過 InnoDB 的表鎖嗎?

我:沒用過,InnoDB 的表鎖很雞肋,我知道:

LOCK TABLES yes READ 是對 yes 這個表上 S 鎖。
LOCK TABLES yes WRITE 是對 yes 這個表上 X 鎖。
但是基本上沒用。

面試官:噢?怎麼個雞肋了?

我:平日的 update 、select 要用也是用行鎖了,不可能用粒度粗的表鎖。唯一能想到用上表鎖的就是 DDL 語句了,比如 ALTER TABLE 的時候,應該鎖定整個表,防止查詢和修改。

但是這個 server 已經提供了一個叫 MDL 的東西,即 Metadata Locks,所以已經用 MDL 來阻塞了,表鎖也就排不上用場了。

真要用表鎖,估計也就是數據恢復的時候,手動鎖表還原數據了。

面試官摸了摸頭上的反光處:可以,但是如果真要到用表鎖的時候,那表鎖和行鎖之間不是會衝突的嗎?如果表裏面已經加了行鎖怎麼辦?得一條記錄一條記錄遍歷過去找行鎖嗎?

我:這確實是一種實現方式,但是性能太差了,假設數據庫裏有上千萬的數據,這加個表鎖得找死。

所以有了個叫意向鎖(Intention Locks)的東西。

IS(Intention Shared Lock),共享意向鎖
IX(Intention Exclusive Lock),獨佔意向鎖。
這兩個鎖是表級別的鎖,當需要對錶中的某條記錄上 S 鎖的時候,先在表上加個 IS 鎖,表明此時表內有 S 鎖。當需要對錶中的某條記錄上 X 鎖的時候,先在表上加個 IX 鎖,表明此時表內有 X 鎖。

這樣操作之後,如果要加表鎖,就不需要遍歷所有記錄去找了,直接看看錶上面有沒有 IS 和 IX 鎖。

比如,此時要上表級別的 S 鎖,如果表上沒有 IX ,說明表中沒有記錄有獨佔鎖,其實就可以直接上表級 S 鎖。

如果此時要上表級別的 X 鎖,如果表上沒有 IX 和 IS ,說明表中的所有記錄都沒加鎖,其實就可以直接上表級 X 鎖。

因此 IS 和 IX 的作用就是在上表級鎖的時候,可以快速判斷是否可以上鎖,而不需要遍歷表中的所有記錄。

所以 IS 和 IX 互相之間是不會衝突的,因爲它們的作用只是打個標記,來豐富一下上面的表格:

面試官:行,那再來說說行鎖吧,InnoDB 有幾類行鎖?

我:有記錄鎖(Record Locks)、間隙鎖(Gap Locks)、Next-Key Locks。

面試官:詳細說說看?

我:記錄鎖顧名思義就是鎖住當前的記錄,它是作用到索引上的。我們都知道 innodb 是肯定有索引的,即使沒有主鍵也會創建隱藏的聚簇索引,所以記錄鎖總是鎖定索引記錄。

比如,此時一個事務 A 執行 SELECT * FROM yes WHERE name = 'xx' FOR UPDATE; 那麼 name = xx 這條記錄就被鎖定了,其他事務無法插入、刪除、修改 name = xx 的記錄。

此時事務 A 還未提交,另一個事務 B 要執行 insert into yes (name) values ('xx'),此時會被阻塞,這個很好理解。

但是,如果另一個事務 C 執行了 insert into yes (name) values ('aa'),這個語句會被阻塞嗎?

看情況。

如果 name 沒有索引。前面提到記錄鎖是加到索引上的,但是 name 沒索引啊,那隻能去找聚簇索引,但聚簇索引上面只有主鍵啊,它哪知道各自的 name 是什麼,所以咋辦?都鎖了唄!

因此,如果 name 沒有索引,那麼事務 C 會被阻塞,如果有索引,則不會被阻塞!

所以這裏要注意,沒索引的列不要輕易的鎖,不要以爲有行鎖就可以爲所欲爲,並不是這樣滴。

面試官:喲,有點東西,繼續繼續。

我:然後是間隙鎖,這個東西它有點東西。

前面說了,記錄鎖需要加到記錄上,但是如果要給此時還未存在的記錄加鎖怎麼辦?也就是要預防幻讀的出現!

這時候間隙鎖就派上用場了,它是給間隙加上鎖。

比如此時有 1、3、5、10 這四條記錄,之前的文章分析過,數據頁中還有兩條虛擬的記錄,分別是 Infimum 和 Supremum。

可以看到,記錄之前都有間隙,那間隙鎖呢,鎖的就是這個間隙!

比如我把 3 和 5 之間的間隙鎖了,此時要插入 id = 4 的記錄,就會被這個間隙鎖給阻塞了,這樣就避免了幻讀的產生!也就實現了鎖定未插入的記錄的需求!

還有個 Next-Key Locks 就是記錄鎖+間隙鎖,像上面間隙鎖的舉例,只能鎖定(3,5) 這個區間,而 Next-Key Locks 是一個前開後閉的區間(3,5],這樣能防止查詢 id=5 的這個幻讀。

面試官:那間隙鎖之間會不會衝突?

我 :不會,間隙鎖的唯一目的就是防止其他事務插入數據到間隙中 ,所以即使兩個間隙鎖要鎖住相同的間隙也沒有關係,因爲它們的目的是一致的,所以不衝突。

面試官:那間隙鎖可以顯式禁用嗎?

我 :可以的。間隙鎖是在事務隔離級別爲可重複讀的時候生效的,如果將事務隔離級別更改爲 READ COMMITTED,就會禁用了,此時,間隙鎖對於搜索和索引掃描是禁用的,僅用於外鍵約束檢查和重複鍵檢查。

面試官:說到間隙鎖,那你知道什麼是插入意向鎖嗎?

我:插入意向鎖,即 Insert Intention Locks,它也是一類間隙鎖,但是它不是鎖定間隙,而是等待某個間隙。比如上面舉例的 id = 4 的那個事務 C ,由於被間隙鎖給阻塞了,所以事務 C 會生成一個插入意向鎖,表明等待這個間隙鎖的釋放。

並且插入意向鎖之間不會阻塞,因爲它們的目的也是隻等待這個間隙被釋放,所以插入意向鎖之間沒有衝突。

面試官:所以這個插入意向鎖其實沒什麼用的?

我:確實,它的目的不在於鎖定資源防止別人訪問,我個人覺得更像是爲了遵循 MySQL 的鎖代碼實現而爲之。

鎖其實就是內存裏面的一個結構,每個事務爲某個記錄或者間隙上鎖就是創建一個鎖對象來爭搶資源。

如果某個事務沒有搶到資源,那也會生成一個鎖對象,只是狀態是等待的,而當擁有資源的事務釋放鎖之後,就會尋找正在等待當前資源的鎖結構,然後選一個讓它獲得資源並喚醒對應的事務使之得以執行。

所以按照這麼個邏輯,那些在等待間隙鎖的插入事務,也需要對應的建立一個鎖結構,然後鎖類型是插入意向鎖。

這樣一來,間隙鎖的事務在釋放間隙鎖的時候,才能得以找到那些等待插入的事務,然後進行喚醒,而由鎖的類型也可以得知是插入意向鎖,之間不需要阻塞,所以可以一起執行插入。

面試官:說到插入新記錄我問你個問題,如果插入的事務還未提交,現在有另一個事務通過SELECT ... LOCK IN SHARE MODE 或者SELECT ... FOR UPDATE 打算讀取這條記錄怎麼辦?此時生效的是什麼鎖?

我:(我丟,面試官想給我挖坑?哼,但是這難不倒我霸中霸!)

插入的事務還未提交,此時普通 select 沒問題,有 MVCC 在,所以讀不到未提交的版本。而 SELECT ... LOCK IN SHARE MODE或者SELECT ... FOR UPDATE` 是要獲取記錄 S 鎖和 X 鎖的,但是此時事務還未提交,因此這兩類 select 會阻塞。

具體是怎麼阻塞的呢?因爲有事務ID!通過 MVCC 可以利用事務ID 來進行判斷當前記錄是否可見,這其實相當於一把隱式鎖!知道當前記錄不可見,於是這個查詢事務會爲之前未提交的插入的事務生成一個鎖結構,然後查詢事務自己也生成鎖結構,接着等待插入事務的釋放,這樣就完成了阻塞!

面試官:(這小子,我要壓不住他了!)行,那你知道什麼是 AUTO-INC Locks 鎖嗎?

我:知道,Auto-Inc Lock 是一個特殊的表級鎖,用於自增列插入數據時使用。在插入一條數據的時候,需要在表上加個 Auto-Inc Lock,然後爲自增列分配遞增的值,在語句插入結束之後,再釋放 Auto-Inc Lock。

在 MySQL 5.1.22 版本之後,又弄了個互斥量來進行自增減的累加。互斥量的性能高於 Auto-Inc Lock,因爲 Auto-Inc Lock是語句插入完畢之後才釋放鎖,而互斥量是在語句插入的時候,獲得遞增值之後,就可以釋放鎖,所以性能更好。

但是我們還需要考慮主從的情況,由於併發插入的情況,基於 statement -based binlog 複製時,自增的值順序無法把控,可能會導致主從數據不一致。

所以 MySQL 有個 innodb_autoinc_lock_mode 配置,一共有三個值:

0,只用 Auto-Inc Lock。
1,默認值,對於插入前已知插入行數的插入,用互斥量,對於插入前不知道具體插入數的插入,用 Auto-Inc Lock,這樣即使基於 statement -based binlog 複製也是安全的。
2,只用互斥量。
面試官:那 MyISAM 有 AUTO-INC Locks 鎖嗎?

我:沒啊,MyISAM 插入本來就用了表鎖。

面試官:(這小子行啊,我得找回行子)那你還知道 MySQL 有什麼鎖嗎?

我:(這還沒問夠??)表鎖、IS、IX、MDL、記錄鎖、間隙鎖、Next-key locks、插入意向鎖、Auto-Inc Locks,還有啥?

面試官瞥了我一眼:(好小子,總算治了你了)不知道了?

我:(該慫的時候,還是得慫)知識盲區了,請面試官教教我。

面試官:還有個 Predicate Locks,謂詞鎖。

我:什麼玩意?

面試官:InnoDB 是支持空間數據的,所以有空間索引,爲了處理涉及空間索引的操作的鎖定,next-key locking 不好使,因爲多維數據中沒有絕對排序的概念,因此不清楚“下一個” key 在哪。

所以爲了支持具有空間索引的表的隔離級別,InnoDB使用謂詞鎖。

空間索引包含最小邊界矩形(MBR)值,因此 InnodB 通過在用於查詢的 MBR 值上設置謂詞鎖定,使得 InnoDB 在索引上執行一致性讀, 其他事務無法插入或修改與查詢條件匹配的行。

我:(.....果然超出了我的知識範圍,這個B被他裝到了)老面試官您真的是66666。

面試官:行吧,你今天答的馬馬虎虎還可以,下次再問問你啥 buffer pool、change buffer、doublewrite buffer 啥的。

我:(????還問呢),這是還繼續面嗎?這算三面嗎?

面試官:你管我,我就想多面面你,充分了解一下,我們公司很嚴格的,人不是隨便招的!趕緊回去等通知!

我:行行行,您老等着,就一堆 buffer 是吧,我回去好好準備準備哈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章