在第20和21篇文章中,我和你介紹了InnoDB的間隙鎖、next-key lock,以及加鎖規則。在這兩篇文章的評論區,出現了不少高質量的留言。我以爲經過分析這些問題,能夠幫助你加深對加鎖規則的理解。session
因此,我就從中挑選了幾個有表明性的問題,構成了今天這篇答疑文章的主題,即:用動態的觀點看加鎖。數據結構
爲了方便你理解,咱們再一塊兒複習一下加鎖規則。這個規則中,包含了兩個「原則」、兩個「優化」和一個「bug」:併發
接下來,咱們的討論仍是基於下面這個表t:優化
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) ) ENGINE=InnoDB; insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
有同窗對「等值查詢」提出了疑問:等值查詢和「遍歷」有什麼區別?爲何咱們文章的例子裏面,where條件是不等號,這個過程裏也有等值查詢?spa
咱們一塊兒來看下這個例子,分析一下這條查詢語句的加鎖範圍:線程
begin; select * from t where id>9 and id
利用上面的加鎖規則,咱們知道這個語句的加鎖範圍是主鍵索引上的 (0,5]、(5,10]和(10, 15)。也就是說,id=15這一行,並無被加上行鎖。爲何呢?3d
咱們說加鎖單位是next-key lock,都是前開後閉區間,可是這裏用到了優化2,即索引上的等值查詢,向右遍歷的時候id=15不知足條件,因此next-key lock退化爲了間隙鎖 (10, 15)。code
可是,咱們的查詢語句中where條件是大於號和小於號,這裏的「等值查詢」又是從哪裏來的呢?對象
要知道,加鎖動做是發生在語句執行過程當中的,因此你在分析加鎖行爲的時候,要從索引上的數據結構開始。這裏,我再把這個過程拆解一下。blog
如圖1所示,是這個表的索引id的示意圖。
首先這個查詢語句的語義是order by id desc,要拿到知足條件的全部行,優化器必須先找到「第一個id<12的值」。
這個過程是經過索引樹的搜索過程獲得的,在引擎內部,實際上是要找到id=12的這個值,只是最終沒找到,但找到了(10,15)這個間隙。
而後向左遍歷,在遍歷過程當中,就不是等值查詢了,會掃描到id=5這一行,因此會加一個next-key lock (0,5]。
也就是說,在執行過程當中,經過樹搜索的方式定位記錄的時候,用的是「等值查詢」的方法。
與上面這個例子對應的,是@發條橙子同窗提出的問題:下面這個語句的加鎖範圍是什麼?
begin; select id from t where c in(5,20,10) lock in share mode;
這條查詢語句裏用的是in,咱們先來看這條語句的explain結果。
能夠看到,這條in語句使用了索引c而且rows=3,說明這三個值都是經過B+樹搜索定位的。
在查找c=5的時候,先鎖住了(0,5]。可是由於c不是惟一索引,爲了確認還有沒有別的記錄c=5,就要向右遍歷,找到c=10才確認沒有了,這個過程知足優化2,因此加了間隙鎖(5,10)。
一樣的,執行c=10這個邏輯的時候,加鎖的範圍是(5,10] 和 (10,15);執行c=20這個邏輯的時候,加鎖的範圍是(15,20] 和 (20,25)。
經過這個分析,咱們能夠知道,這條語句在索引c上加的三個記錄鎖的順序是:先加c=5的記錄鎖,再加c=10的記錄鎖,最後加c=20的記錄鎖。
你可能會說,這個加鎖範圍,不就是從(5,25)中去掉c=15的行鎖嗎?爲何這麼麻煩地分段說呢?
由於我要跟你強調這個過程:這些鎖是「在執行過程當中一個一個加的」,而不是一次性加上去的。
理解了這個加鎖過程以後,咱們就能夠來分析下面例子中的死鎖問題了。
若是同時有另一個語句,是這麼寫的:
select id from t where c in(5,20,10) order by c desc for update;
此時的加鎖範圍,又是什麼呢?
咱們如今都知道間隙鎖是不互鎖的,可是這兩條語句都會在索引c上的c=五、十、20這三行記錄上加記錄鎖。
這裏你須要注意一下,因爲語句裏面是order by c desc, 這三個記錄鎖的加鎖順序,是先鎖c=20,而後c=10,最後是c=5。
也就是說,這兩條語句要加鎖相同的資源,可是加鎖順序相反。當這兩條語句併發執行的時候,就可能出現死鎖。
關於死鎖的信息,MySQL只保留了最後一個死鎖的現場,但這個現場仍是不完備的。
有同窗在評論區留言到,但願我能展開一下怎麼看死鎖。如今,我就來簡單分析一下上面這個例子的死鎖現場。
圖3是在出現死鎖後,執行show engine innodb status命令獲得的部分輸出。這個命令會輸出不少信息,有一節LATESTDETECTED DEADLOCK,就是記錄的最後一次死鎖信息。
咱們來看看這圖中的幾個關鍵信息。
這個結果分紅三部分:
第一個事務的信息中:
第二個事務顯示的信息要多一些:
從上面這些信息中,咱們就知道:
「lock in share mode」的這條語句,持有c=5的記錄鎖,在等c=10的鎖;
「for update」這個語句,持有c=20和c=10的記錄鎖,在等c=5的記錄鎖。
所以致使了死鎖。這裏,咱們能夠獲得兩個結論:
因爲鎖是一個個加的,要避免死鎖,對同一組資源,要按照儘可能相同的順序訪問;
在發生死鎖的時刻,for update 這條語句佔有的資源更多,回滾成本更大,因此InnoDB選擇了回滾成本更小的lock in share mode語句,來回滾。
看完死鎖,咱們再來看一個鎖等待的例子。
在第21篇文章的評論區,@Geek_9ca34e 同窗作了一個有趣驗證,我把復現步驟列出來:
能夠看到,因爲session A並無鎖住c=10這個記錄,因此session B刪除id=10這一行是能夠的。可是以後,session B再想insert id=10這一行回去就不行了。
如今咱們一塊兒看一下此時show engine innodb status的結果,看看能不能給咱們一些提示。鎖信息是在這個命令輸出結果的TRANSACTIONS這一節。你能夠在文稿中看到這張圖片
咱們來看幾個關鍵信息。
index PRIMARY of table `test`.`t` ,表示這個語句被鎖住是由於表t主鍵上的某個鎖。
lock_mode X locks gap before rec insert intention waiting 這裏有幾個信息:
那麼這個gap是在哪一個記錄以前的呢?接下來的0~4這5行的內容就是這個記錄的信息。
n_fields 5也表示了,這一個記錄有5列:
所以,咱們就知道了,因爲delete操做把id=10這一行刪掉了,原來的兩個間隙(5,10)、(10,15)變成了一個(5,15)。
說到這裏,你能夠聯合起來再思考一下這兩個現象之間的關聯:
session A執行完select語句後,什麼都沒作,但它加鎖的範圍忽然「變大」了;
第21篇文章的課後思考題,當咱們執行select * from t where c>=15 and c<=20 order by c desc lock in share mode; 向左掃描到c=10的時候,要把(5, 10]鎖起來。
也就是說,所謂「間隙」,其實根本就是由「這個間隙右邊的那個記錄」定義的。
看過了insert和delete的加鎖例子,咱們再來看一個update語句的案例。在留言區中@信信 同窗作了這個試驗:
你能夠本身分析一下,session A的加鎖範圍是索引c上的 (5,10]、(10,15]、(15,20]、(20,25]和(25,supremum]。
注意:根據c>5查到的第一個記錄是c=10,所以不會加(0,5]這個next-key lock。
以後session B的第一個update語句,要把c=5改爲c=1,你能夠理解爲兩步:
插入(c=1, id=5)這個記錄;
刪除(c=5, id=5)這個記錄。
按照咱們上一節說的,索引c上(5,10)間隙是由這個間隙右邊的記錄,也就是c=10定義的。因此經過這個操做,session A的加鎖範圍變成了圖7所示的樣子:
好,接下來session B要執行 update t set c = 5 where c = 1這個語句了,同樣地能夠拆成兩步:
插入(c=5, id=5)這個記錄;
刪除(c=1, id=5)這個記錄。
第一步試圖在已經加了間隙鎖的(1,10)中插入數據,因此就被堵住了。
今天這篇文章,我用前面第20和第21篇文章評論區的幾個問題,再次跟你複習了加鎖規則。而且,我和你重點說明了,分析加鎖範圍時,必定要配合語句執行邏輯來進行。
在我看來,每一個想認真瞭解MySQL原理的同窗,應該都要可以作到:經過explain的結果,就可以腦補出一個SQL語句的執行流程。達到這樣的程度,纔算是對索引組織表、索引、鎖的概念有了比較清晰的認識。你一樣也能夠用這個方法,來驗證本身對這些知識點的掌握程度。
在分析這些加鎖規則的過程當中,我也順便跟你介紹了怎麼看show engine innodb status輸出結果中的事務信息和死鎖信息,但願這些內容對你之後分析現場能有所幫助。
老規矩,即使是答疑文章,我也仍是要留一個課後問題給你的。
上面咱們提到一個很重要的點:所謂「間隙」,其實根本就是由「這個間隙右邊的那個記錄」定義的。
那麼,一個空表有間隙嗎?這個間隙是由誰定義的?你怎麼驗證這個結論呢?
你能夠把你關於分析和驗證方法寫在留言區,我會在下一篇文章的末尾和你討論這個問題。感謝你的收聽,也歡迎你把這篇文章分享給更多的朋友一塊兒閱讀。
我在上一篇文章最後留給的問題,是分享一下你關於業務監控的處理經驗。
在這篇文章的評論區,不少同窗都分享了不錯的經驗。這裏,我就選擇幾個比較典型的留言,和你分享吧:
這些都是很好的經驗,你也能夠根據具體的業務場景借鑑適合本身的方案。