InnoDB中的鎖 - MySQL 8.0官方文檔筆記(一)

背景

最近看MySQL官方文檔比較多,在此開坑翻譯部分篇章,並附上一些旁註,用於展現實操結果,或者表達我的理解。html

文檔版本:8.0
來源:innodb-lockingmysql

此類形式爲旁註。
本篇主要介紹InnoDB中的各種鎖,而鎖觸發條件和應用場景不全在此篇中說起,後續會單獨成篇進行講解。算法

共享鎖 & 獨佔鎖

InnoDB 實現了兩種類型的標準行鎖:共享(S)鎖和獨佔(X)鎖。(下文簡稱S鎖和X鎖)sql

  • S鎖容許持有該鎖的事務讀取一行記錄
  • X鎖容許持有該鎖的事務更新或刪除一行記錄

若是事務 T1 持有行 R 的S鎖,另外一個事務 T2 在行 R 上嘗試獲取鎖,會有以下情景:數據庫

  • T2 請求 S 鎖,能夠直接得到。此時 T1 和 T2 都持有行 R 的S 鎖。
  • T2 請求 X 鎖,不能直接得到。

若是T1 持有行 R 的 X鎖,另外一個事務 T2 在行 R 上嘗試獲取任何一種鎖,都不能直接得到。T2 必須等待T1 釋放行R 上的鎖。數據結構

這裏講到的S/X鎖更傾向於在描述鎖的模型:即鎖的獲取方式、資源控制能力和鎖之間的交互。併發

接下來所講到的各種鎖是基於S/X模型來實現的,不一樣在於粒度、強弱等。性能

意向鎖

InnoDB支持多粒度鎖:即行鎖與表鎖共存。例如語句LOCK TABLES ... WRITE 獲取表的X鎖。InnoDB使用 意向鎖 實如今多個粒度
上加鎖。意向鎖是表鎖,用於指明一個事務稍後要獲取哪一種類型的行鎖(S or X)。意向鎖有兩種類型:spa

  • 共享意向鎖(IS):指明事務將要獲取行的共享鎖
  • 獨佔意向鎖(IX):指明事務將要獲取行的獨佔鎖

例如,SELECT ... FOR SHARE獲取了 IS 鎖,SELECT ... FOR UPDATE 獲取了 IX 鎖。翻譯

注意5.6\5.7版本獲取IS鎖的語句有所不一樣:SELECT ... LOCK IN SHARE MODE

意向鎖的使用原則:
一個事務若要獲取行的 S 鎖,必須先獲取該表的 IS 鎖或更強級別的鎖。
一個事務若要獲取行的 X 鎖,必須先獲取該表的 IX 鎖。

表鎖之間的兼容性總結以下:


X IX S IS
X 衝突 衝突 衝突 衝突
IX 衝突 兼容 衝突 兼容
S 衝突 衝突 兼容 兼容
IS 衝突 兼容 兼容 兼容

事務請求的鎖必須和目前已產生的鎖兼容,不然沒法獲取,直到衝突鎖釋放。而等待釋放的過程若是InnoDB檢測到存在死鎖,則會拋出錯誤。

意向鎖不會阻塞其餘鎖請求,除了表鎖(如 LOCK TABLES ... WRITE)。意向鎖是爲了代表事務正在嘗試獲取,或將要獲取行鎖。

若是要查看當前數據庫中的意向鎖,執行 SHOW ENGINE INNODB STATUS , InnoDB 的監視器會輸出以下內容:

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

若是在輸出日誌中看不到鎖的相關信息,須要開啓以下參數:
SET GLOBAL innodb_status_output_locks=ON;
見 : innodb-enabling-monitors

記錄鎖

記錄鎖用於鎖住一條索引值。例如語句 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 8000000 a; asc ;;
 1 : len 6 ; hex 00000000274 f; asc 'O;;
 2 : len 7 ; hex b60000019d0110; asc ;;

trx id 10078 lock_mode X locks rec but not gap 意味記錄鎖只鎖住了單條記錄而沒有鎖定任何間隙,這也是一般主鍵查詢的結果,關於間隙的概念下文中會介紹到。

針對查詢條件沒有覆蓋索引時的狀況,進行實驗:
1.在一個表中加入自增主鍵,插入若干記錄
2.刪除主鍵列的索引
3.以原主鍵列的值做爲查詢條件執行SELECT FOR UPDATE

監視器輸出:RECORD LOCKS space id 3 page no 6 n bits 320 index GEN_CLUST_INDEX of table `test`.`t` trx id 2131 lock_mode X
也就是使用了隱式生成的聚簇索引。
在這種狀況下即便查詢條件中的列在值上是惟一的,也會鎖定全表記錄(由於走了全表掃描)。
此時開啓另外一個事務,對另外一條記錄執行主鍵加鎖查詢(SELECT FOR UPDATE),根據S/X鎖標準將被阻塞,經驗證確實如此。

間隙鎖

間隙鎖用於鎖定索引記錄之間的間隙,或者一組索引值兩端的間隙。好比語句 SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
能夠防止其餘事務在t.c1列上插入 15 (由於在10-20之間),不管 15 是否是列上已有的值,由於在 BETWEEN 所指定的區間都被鎖住了。

所謂的間隙能夠覆蓋一個值,多個值,甚至是 0 個。

關於間隙的準確含義此處引用官方術語集:
間隙 指能在InnoDB索引數據結構中能被插入的位置。例如用SELECT ... FOR UPDATE 鎖住一批行時,InnoDB將鎖住條件命中的索引上的值以及它們之間的間隙。好比加鎖讀全部大於10的值時,間隙鎖會防止其餘事務插入大於10的值。

間隙鎖必定程度上體現了MySQL在性能與併發之間的權衡,在某些特定的事務隔離級別中使用到了間隙鎖。

對於使用惟一索引查找的語句,不會用到間隙鎖(除非搜索條件中只包含一個多列惟一索引的部分列)例以下列語句中,若是列 id 有惟一索引,則只會用到一個 id=100 的記錄鎖,而且不會妨礙其它會話在以前的間隙進行插入。

1 SELECT * FROM child WHERE id = 100 ;

但若是id沒有索引或者有一個非惟一索引,語句就會鎖住以前的間隙。

不一樣的事務能夠在同一段間隙上持有相互衝突的鎖。例如,事務A持有一段間隙的共享間隙鎖(gap S-lock),同時事務B能夠在同一段間隙上持有獨佔間隙鎖(gap X-lock)。由於若是一條索引記錄被刪除,不一樣事務針對該記錄持有的間隙鎖必須被合併。

在InnoDB中,間隙鎖的互斥特性被至關程度地抑制了,意思是間隙鎖的惟一做用是防止事務在間隙中進行插入操做。間隙鎖能夠共存。不一樣事務能夠同時持有同一段間隙的間隙鎖。共享間隙鎖和獨佔間隙鎖沒有區別。它們之間不會衝突,且做用相同。

間隙鎖能夠被顯式禁用。經過改變事務隔離級別爲 READ COMMITTED 或者開啓系統變量 innodb_locks_unsafe_for_binlog(目前已棄用)
來禁用。在這些狀況下,間隙鎖再也不用於搜索和索引掃描,而只用於外鍵約束檢查和重複鍵檢查。

上述的兩種設置還有一些「反作用」。在MySQL計算出where條件後,不匹配的行的記錄鎖會被釋放。對於UPDATE語句,InnoDB會執行一個「半一致性」讀,從而向MySQL返回最新提交的版本號,用於挑選出匹配WHERE條件的行。

針對最後一段提到關於 READ COMMITTED 會釋放不匹配的記錄鎖進行實驗。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; 
start TRANSACTION;  
-- id未加索引  
SELECT * from test.t where id =1 for update;

監視器輸出:

RECORD LOCKS space id 3 page no 6 n bits 320 index >GEN_CLUST_INDEX of table `test`.`t` trx id 2178 lock_mode X locks rec but not gap

能夠看到雖然走聚簇索引,但事務最終只佔有符合篩選的記錄鎖。
隔離級別改回READ COMMITTED,執行相同語句。
監視器輸出:

3 lock struct(s), heap size 1136, 372 row lock(s)  
RECORD LOCKS space id 3 page no 6 n bits 320 index GEN_CLUST_INDEX of table `test`.`t` trx id 2179 lock_mode X

佔有全表記錄鎖。

鄰鍵鎖

鄰鍵鎖是記錄鎖以及在索引記錄以前間隙上的間隙鎖的組合。

InnoDB在搜索或掃描索引時,對遇到的每一條索引記錄設置共享/獨佔鎖,以此實現行鎖。所以,所謂的行鎖實際就是記錄鎖。而鄰鍵鎖不只僅鎖住一條索引記錄,還會影響記錄以前的「間隙」。也就是說鄰鍵鎖能夠表示爲一個記錄鎖加上記錄以前間隙的間隙鎖。若是某個會話持有記錄R上索引的共享/獨佔記錄鎖,對於其它會話,若是插入的值小於記錄R上索引值(按索引排序規則),則不能直接插入,必須等待鎖釋放。

設想一個索引包含值 10 , 11 , 13 , 20 。那麼全部可能的鄰鍵鎖區間以下,圓括號表明不包含,方括號表明包含:

(負無窮, 10 ]  
 ( 10 , 11 ]  
 ( 11 , 13 ]  
 ( 13 , 20 ]  
 ( 20 , 正無窮)

對於最後一個區間,鄰鍵鎖鎖定一段大於最大索引值的間隙,並使用一個虛擬的紀錄表示上界。這個上界並非真實的索引值,因此實際上這個鄰鍵鎖沒有攜帶記錄鎖,只有大於當前索引最大值的間隙鎖。

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 73757072656 d756d; asc supremum;;
 Record lock, heap no 2 PHYSICAL RECORD: n_fields 3 ; compact format; info bits 0
 0 : len 4 ; hex 8000000 a; asc ;;
 1 : len 6 ; hex 00000000274 f; asc 'O;;
 2 : len 7 ; hex b60000019d0110; asc ;;

鄰鍵鎖主要用於解決幻行問題:事務因其它事務的插入操做致使兩次讀取的結果集不一致。鄰鍵鎖解決了這一問題,可幫助應用實現插入值惟一(加鎖讀->獲取鄰鍵鎖->只容許本會話插入)。

插入意向鎖

插入意向鎖是一種特殊的間隙鎖,在插入操做中執行行插入以前得到。用於標誌插入的意向,從而使多個事務在同一段間隙執行插入時,若是對方不在同一個索引值位置上插入,則無需互相等待。例如,當前索引值有 4 和 7 。兩個事務分別準備插入 5 和 6 ,在獲取被插入行的獨佔鎖以前,它們會各自獲取 4 至 7 之間間隙的插入意向鎖,且不會互相阻塞,由於插入值沒有衝突。

下面經過一個例子來演示事務在獲取記錄的獨佔鎖以前,獲取插入意向鎖的過程。案例中涉及兩個客戶端,分別是A和B。

客戶端A建立一張表,包含兩條索引值( 90 和 102),而後開啓一個事務,獲取ID大於100的全部記錄的獨佔鎖。獨佔鎖將包含一個 id<102 的間隙鎖:

即鄰鍵鎖,區間 =(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 的監視器會輸出以下內容:

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

自增鎖

自增鎖是一種特殊的表鎖,事務在帶有自增列的表中執行插入會獲取自增鎖。在最簡單的情形中,若是某個事務在向表中插入數據,其它事務必須等待其插入完畢才能執行本身的插入,以此來保證主鍵值是連續的。

配置項 innodb_autoinc_lock_mode 用於控制自增鎖使用的算法,以幫助你在自增序列的可預測性和插入的併發能力之間權衡。

針對空間索引的斷言鎖

InnoDB支持對空間行創建索引。
在處理涉及到空間索引的操做時,鄰鍵鎖在 REPEATABLE READSERIALIZABLE 兩個隔離級別上不能很好的工做。由於對於多維數據沒有絕對的排序規則,因此並不能明確誰纔是「鄰鍵」。

相關文章
相關標籤/搜索