最近看MySQL官方文檔比較多,在此開坑翻譯部分篇章,並附上一些旁註,用於展現實操結果,或者表達我的理解。html
文檔版本:8.0
來源:innodb-lockingmysql
此類形式爲旁註。
本篇主要介紹InnoDB中的各種鎖,而鎖觸發條件和應用場景不全在此篇中說起,後續會單獨成篇進行講解。算法
InnoDB 實現了兩種類型的標準行鎖:共享(S)鎖和獨佔(X)鎖。(下文簡稱S鎖和X鎖)sql
若是事務 T1 持有行 R 的S鎖,另外一個事務 T2 在行 R 上嘗試獲取鎖,會有以下情景:數據庫
若是T1 持有行 R 的 X鎖,另外一個事務 T2 在行 R 上嘗試獲取任何一種鎖,都不能直接得到。T2 必須等待T1 釋放行R 上的鎖。數據結構
這裏講到的S/X鎖更傾向於在描述鎖的模型:即鎖的獲取方式、資源控制能力和鎖之間的交互。併發
接下來所講到的各種鎖是基於S/X模型來實現的,不一樣在於粒度、強弱等。性能
InnoDB支持多粒度鎖:即行鎖與表鎖共存。例如語句LOCK TABLES ... WRITE
獲取表的X鎖。InnoDB使用 意向鎖 實如今多個粒度
上加鎖。意向鎖是表鎖,用於指明一個事務稍後要獲取哪一種類型的行鎖(S or X)。意向鎖有兩種類型:spa
例如,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 READ
和 SERIALIZABLE
兩個隔離級別上不能很好的工做。由於對於多維數據沒有絕對的排序規則,因此並不能明確誰纔是「鄰鍵」。