MySQL中InnoDB上的鎖分類

概要

文章翻譯自15.7.1 InnoDB Locking,其中所有鎖特性都以mysql-8.0版本爲準,其中包含的鎖種類如下:

  • 共享鎖和排它鎖
  • 意向鎖
  • 記錄鎖
  • 間隙鎖
  • 後鍵鎖
  • 插入意向鎖
  • 自增鎖
  • 空間索引的謂詞鎖

共享鎖和排它鎖(Shared and Exclusive Locks)

Innodb實現了標準的行級鎖,包括共享鎖和排它鎖兩種類型

  • 共享鎖(s鎖)允許擁有該鎖的事務讀取該行的記錄
  • 排它鎖(x鎖)允許擁有該鎖的事務更新或刪除該行的記錄

如果事務T1擁有行r上的共享鎖,那麼對於不同的事務T2的對行r上的請求,有以下處理方式:

  • 可以立即授予T2共享鎖,最終T1和T2都擁有共享鎖
  • 不能立即授予T2排它鎖

如果事務T1擁有行r上的排它鎖,則對於不同的事務T2,不能授予其行r上的任何鎖,T2必須等待T1釋放其對行r的鎖定

意向鎖(Intention Locks)

InnoDB支持多種粒度的鎖定,來允許行鎖和表鎖同時存在,例如LOCK TABLE ... WRITE語句可以在指定的表上使用排它鎖。爲了實現多粒度鎖定,InnoDB使用了意向鎖

意向鎖是表級鎖,它表示一個事務稍後對錶中的行需要使用的鎖類型,意向鎖有兩種類型:

  • 意向共享鎖(IS鎖)表示一個事務傾向於對錶中的部分行設置共享鎖
  • 意向排它鎖(IX鎖)表示一個事務傾向於對錶中的部分行設置排它鎖

例如,SELECT ... FOR SHARE會設置IS鎖,SELECT ... FOR UPDATE會設置IX鎖

意向鎖的協議如下:

  • 在事務可以獲取表中某行的共享鎖之前,必須先在表上獲取IS鎖,或表上更強的鎖
  • 在事務可以獲取表中某些行的排它鎖之前,必須先在表上獲取IX鎖

表級鎖的兼容性通過以下的矩陣進行了總結:
兼容性矩陣
如果請求事務與當前存在的鎖兼容,則授予鎖。如果衝突則不會授予,事務會進行等待,直到衝突的鎖被釋放。永遠不會在衝突情況下授予鎖,因爲會導致數據庫的死鎖

意向鎖不會阻塞除全表請求外的任何內容(如LOCK TABLES ... WRITE),意向鎖的主要目的是顯示某人正在或正要鎖定表中的行

意向鎖鎖定的事務數據在SHOW ENGINE INNODB STATUS和InnoDB監視器中輸出的內容大致是以下這種形式:

TABLE LOCK table `test`.`t` trx id 10080 lock mode IX

記錄鎖(Record Locks)

記錄鎖,是對索引記錄的鎖定。例如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;會阻止其他事務的插入、更新,以及刪除t表中c1=10的行

即使表中沒有定義索引,記錄鎖也始終會鎖定索引記錄。在這種情況下,InnoDB會創建一個隱藏的聚集索引,並使用此索引來進行記錄鎖定

記錄鎖鎖定的事務數據在SHOW ENGINE INNODB STATUS和InnoDB監視器中輸出的內容大致是以下這種形式:

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t` 
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     'O;;
 2: len 7; hex b60000019d0110; asc        ;;

間隙鎖(Gap Locks)

間隙鎖,鎖定的是索引記錄之間的間隙,或是第一個索引之前以及最後一個索引之後的間隙,如SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;會阻止其他事務將值15插入到t.c1中,無論該列是否存在任何這樣的值,因爲該範圍內的任何存在的值都會被鎖定

這個間隙可能會跨越單個索引、多個索引,甚至是空

間隙鎖是性能和併發能力之間的一些權衡,僅會作用於某些事務隔離級別

使用唯一索引來搜索唯一行的語句不會使用間隙鎖(不包括搜索條件僅包含多列唯一索引的某些列的情況,在這種情況下,還是會使用間隙鎖),比如,如果id列使用了唯一索引,則以下語句僅使用對於id爲100的鎖使用索引記錄鎖,而不關心其他的會話是否在前一個間隙中插入行:

SELECT * FROM child WHERE id = 100;

如果id列沒有索引,或使用的是非唯一索引,則該語句會鎖定前一個間隙

值得注意的是,互相沖突的鎖可以通過不同的事務在間隙上保持,例如,事務A在間隙上保持共享的間隙鎖(間隙S鎖),事務B可以在同一間隙上保持獨佔的間隙鎖(間隙X鎖)。允許互相沖突的間隙鎖的原因是,如果從索引中清除記錄,則必須合併由不同事務保留在記錄上的間隙鎖

InnoDB的間隙鎖是“純粹的抑制”,這意味着間隙鎖的唯一目的是阻止其他事務插入間隙。間隙鎖可以共存,一個事務的間隙鎖不會阻止另一個事務在同一個間隙上進行間隙鎖的鎖定,共享間隙鎖和排他間隙鎖之間沒有區別,彼此不衝突,它們擁有相同的功能

可以通過明確的聲明來禁用間隙鎖,比如將事務隔離級別設置爲READ COMMITTED。在這種情況下,對於搜索和索引掃描會禁用間隙鎖,僅會作用於外鍵約束和重複鍵的檢查

使用READ COMMITTED隔離級別還會有其他影響,MySQL評估WHERE條件後,會釋放不匹配行的記錄鎖。對於UPDATE語句,InnoDB執行“半一致”讀取,使之能將最新的版本提交給MySQL,MySQL就可以確定該行是否與UPDATEWHERE條件匹配

後鍵鎖(Next-Key Locks)

後鍵鎖,是索引記錄上的記錄鎖和索引記錄之前的間隙鎖的組合

InnoDB使用這樣一種方式來進行行級鎖定:當搜索或掃描表索引時,會在遇到的索引記錄上設置共享鎖或排它鎖,因此,行級鎖實際上是索引記錄鎖

索引記錄上的後鍵鎖也會影響該索引記錄之前的“間隙”,也就是說後鍵鎖是索引記錄鎖加上該索引記錄之前的間隙上的間隙鎖。如果一個會話在索引中的記錄R上有共享鎖或排它鎖,則另一個會話不能在索引順序中的R之前的間隙中插入新的索引記錄

假設索引中包含值10、11、13,和20,此索引可能的後鍵鎖會覆蓋以下鍵值,其中圓括號表示排除的間隔端點,方括號表示包含的端點(即開集和閉集的概念):

(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

對於最後一個間隔,後鍵鎖將間隙鎖定在索引中最大值之上,且僞記錄“supermum”的值高於索引中任何的實際值,supermun不是真正的索引記錄,因此,實際上這個後鍵鎖(指最後一個間隙的後鍵鎖)僅鎖定最大索引值之後的間隙

默認情況下,InnoDB在REPEATABLE READ隔離級別下運行,在這種情況下,InnoDB使用後鍵鎖進行搜索和索引掃描,從而防止幻讀

後鍵鎖的事務數據在SHOW ENGINE INNODB STATUS和InnoDB監視器中輸出的內容大致是以下這種形式:

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t` 
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     'O;;
 2: len 7; hex b60000019d0110; asc        ;;

插入意向鎖(Insert Intention Locks)

插入意向鎖,是在行插入之前由INSERT操作設置的一種間隙鎖。該鎖表示以這種方式插入的意圖:如果插入到相同索引間隙中的多個事務,不插入間隙內的相同位置,則不需要等待彼此

假設存在值爲4和7的索引記錄,兩個單獨的事務分別嘗試插入值爲5和6的記錄,在獲取插入行的排他鎖之前,分別使用插入意向鎖來鎖定4到7之間的間隙,但是不會相互阻塞,因爲行爲是不衝突的

以下示例演示了在獲取插入記錄的排它鎖之前,使用插入意向鎖來進行鎖定。該示例涉及兩個客戶端,分別是A和B

客戶端A創建了一個包含兩個索引記錄(99和102)的表,然後啓動一個事務,該事務id對大於100的索引記錄放置排它鎖,這個排它鎖包含記錄102之前的間隙鎖:

mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);

mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id  |
+-----+
| 102 |
+-----+

客戶端B開啓事務,來將記錄插入間隙中,該事務在等待獲取獨佔鎖時使用插入意向鎖:

mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);

插入意向鎖的事務數據在SHOW ENGINE INNODB STATUS和InnoDB監視器中輸出的內容大致是以下這種形式:

RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000002215; asc     " ;;
 2: len 7; hex 9000000172011c; asc     r  ;;...

自增鎖(AUTO-INC Locks)

自增鎖,是由插入到具有AUTO_INCREMENT自增屬性的列中的事務鎖採用的特殊表級鎖。在最簡單的情況下,如果一個事務正在向表中插入值,則其他任何事務都必須等待該表執行自己的插入操作,以便被第一個事務插入的行接收到連續的主鍵值

innodb_autoinc_lock_mode配置選項用來控制用於自增鎖定的算法,它允許您在可預測的自增值序列和插入操作的最大併發量之間權衡

空間索引的謂詞鎖(Predicate Locks for Spatial Indexes)

InnoDB支持包含空間性列的SPATIAL索引(參考11.5.9 Optimizing Spatial Analysis

要處理涉及SPATIAL索引的操作的鎖定,後鍵鎖不能很好地支持REPEATABLE READSERIALIZABLE事務隔離級別,多維數據中沒有絕對有序的概念,因此不能準確地判斷哪一個是“後鍵”

爲了支持包含SPATIAL索引的表的隔離級別,InnoDB使用了謂詞鎖SPATIAL索引包含最小邊界矩形(MBR)值,因此InnoDB在用於查詢的MBR值上設置謂詞鎖來強制對索引進行一致性讀取,其他事務無法插入或修改與查詢條件匹配的行

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