原創: 胡呈清數據庫
前 言網絡
本文是由愛可生運維團隊出品的「MySQL專欄」系列文章,內容來自於運維團隊一線實戰經驗,涵蓋MySQL各類特性的實踐,優化案例,數據庫架構,HA,監控等,有掃雷功效。session
愛可生開源社區持續運營維護的小目標:架構
每週至少推送一篇高質量技術文章運維
每個月研發團隊發佈開源組件新版學習
每一年1024開源一款企業級組件優化
2019年至少25場社區活動spa
歡迎你們持續關注~日誌
在咱們嘗試回答這個問題前,必定要注意前提條件,若是你看過登博的《MySQL 加鎖處理》,必定知道前提不一樣答案也就不一樣,若是還沒看過建議你去看一下,連接:code
那麼這個問題缺乏哪些前提條件?
1. c2 字段建有惟一索引
2. 隔離級別爲:READ-COMMITTED
其實網絡上有相似的案例分析,其中丁奇老師在《MySQL實戰45講》中的第40篇《insert語句的鎖爲何這麼多?》中有同樣的例子和分析,可是個人理解有些微差別,因此來講說我我的的見解,若是有不對的地方請你們指正。首先我會分析一下這個場景的加鎖狀況和死鎖緣由,而後對於差別的點進行展開,最後總結 insert 的加鎖狀況(關於 insert 的加鎖行爲,其實不像 delete 那樣簡單清晰,裏面有一些須要注意的點)。
爲方便你們復現,完整表結構和數據以下:
CREATE TABLE `t3` ( `c1` int(11) NOT NULL AUTO_INCREMENT, `c2` int(11) DEFAULT NULL, PRIMARY KEY (`c1`), UNIQUE KEY `c2` (`c2`) ) ENGINE=InnoDB insert into t3 values(1,1),(15,15),(20,20);
在 session1 執行 commit 的瞬間,咱們會看到 session二、session3 的其中一個報死鎖。這個死鎖是這樣產生的:
**1. **session1 執行 delete 會在惟一索引 c2 的 c2 = 15 這一記錄上加 X lock(也就是在MySQL 內部觀測到的:X Lock but not gap);
**2. **session2 和 session3 在執行 insert 的時候,因爲惟一約束檢測發生惟一衝突,會加 S Next-Key Lock,即對 (1,15] 這個區間加鎖包括間隙,而且被 seesion1 的 X Lock 阻塞,進入等待;
**3. **session1 在執行 commit 後,會釋放 X Lock,session2 和 session3 都得到 S Next-Key Lock;
4. session2 和 session3 繼續執行插入操做,這個時候 INSERT INTENTION LOCK(插入意向鎖)出現了,而且因爲插入意向鎖會被 gap 鎖阻塞,因此 session2 和 session3 互相等待,形成死鎖。
死鎖日誌以下:
在以前的死鎖分析第四點,若是不分析插入意向鎖,也是會形成死鎖的,由於插入最終仍是要對記錄加 X Lock 的,session2 和 session3 仍是會互相阻塞互相等待。
可是插入意向鎖是客觀存在的,咱們能夠在官方手冊中查到,不可忽略:
Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.
插入意向鎖實際上是一種特殊的 gap lock,可是它不會阻塞其餘鎖。假設存在值爲 4 和 7 的索引記錄,嘗試插入值 5 和 6 的兩個事務在獲取插入行上的排它鎖以前使用插入意向鎖鎖定間隙,即在(4,7)上加 gap lock,可是這兩個事務不會互相沖突等待。
當插入一條記錄時,會去檢查當前插入位置的下一條記錄上是否存在鎖對象,若是下一條記錄上存在鎖對象,就須要判斷該鎖對象是否鎖住了 gap。若是 gap 被鎖住了,則插入意向鎖與之衝突,進入等待狀態(插入意向鎖之間並不互斥)。總結一下這把鎖的屬性:
1. 它不會阻塞其餘任何鎖;
2. 它自己僅會被 gap lock 阻塞。
在學習 MySQL 過程當中,通常只有在它被阻塞的時候才能觀察到,因此這也是它經常被忽略的緣由吧...
在此例中,另一個重要的點就是 gap lock,一般狀況下咱們說到 gap lock 都只會聯想到 REPEATABLE-READ 隔離級別利用其解決幻讀。但實際上在 READ-COMMITTED 隔離級別,也會存在 gap lock ,只發生在:惟一約束檢查到有惟一衝突的時候,會加 S Next-key Lock,即對記錄以及與和上一條記錄之間的間隙加共享鎖。
經過下面這個例子就能驗證:
這裏 session1 插入數據遇到惟一衝突,雖然報錯,可是對 (15,20] 加的 S Next-Key Lock 並不會立刻釋放,因此 session2 被阻塞。另一種狀況就是本文開始的例子,當 session2 插入遇到惟一衝突可是由於被 X Lock 阻塞,並不會馬上報錯 「Duplicate key」,可是依然要等待獲取 S Next-Key Lock 。
有個困惑好久的疑問:出現惟一衝突須要加 S Next-Key Lock 是事實,可是加鎖的意義是什麼?仍是說是經過 S Next-Key Lock 來實現的惟一約束檢查,可是這樣意味着在插入沒有遇到惟一衝突的時候,這個鎖會馬上釋放,這不符合二階段鎖原則。這點但願能與你們一塊兒討論獲得好的解釋。
若是是在 REPEATABLE-READ,除以上所說的惟一約束衝突外,gap lock 的存在是這樣的:
普通索引(非惟一索引)的S/X Lock,都帶 gap 屬性,會鎖住記錄以及前1條記錄到後1條記錄的左閉右開區間,好比有[4,6,8]記錄,delete 6,則會鎖住[4,8)整個區間。
對於 gap lock,相信 DBA 們的心情是同樣同樣的,因此個人建議是:
1. 在絕大部分的業務場景下,均可以把 MySQL 的隔離界別設置爲 READ-COMMITTED;
2. 在業務方便控制字段值惟一的狀況下,儘可能減小表中惟一索引的數量。
前面咱們說的 GAP LOCK 實際上是鎖的屬性,另外咱們知道 InnoDB 常規鎖模式有:S 和 X,即共享鎖和排他鎖。鎖模式和鎖屬性是能夠隨意組合的,組合以後的衝突矩陣以下,這對咱們分析死鎖頗有幫助:
無 Unique Key:X Lock but not gap
有 Unique Key:
惟一性約束檢查發生衝突時,會加 S Lock,帶 gap 屬性,會鎖住記錄以及與前1條記錄以前的間隙;
若是插入的位置有帶 gap 屬性的 S/X Lock,則插入意向鎖(LOCK_INSERT_INTENTION)被阻塞,進入等待狀態;
若是新數據順利插入,最後對記錄加 X Lock but not gap。
select、delete 加鎖行爲是很簡單的,剛咱們看了 insert 的加鎖稍有點複雜,那麼 update 是怎麼加鎖的呢?或者更復雜一點的 replace into 呢?歡迎你們一塊兒討論。