本節說明 InnoDB
使用的鎖定類型。
InnoDB
實作標準的資料列層級鎖定,其中有兩種鎖定類型:共用 (S
) 鎖定和互斥 (X
) 鎖定。
如果交易 T1
持有資料列 r
的共用 (S
) 鎖定,則來自某些不同交易 T2
對資料列 r
鎖定的要求會按如下方式處理:
T2
對S
鎖定的要求可以立即授予。因此,T1
和T2
都持有r
的S
鎖定。T2
對X
鎖定的要求無法立即授予。
如果交易 T1
持有資料列 r
的互斥 (X
) 鎖定,則來自某些不同交易 T2
對 r
上任一類型鎖定的要求都無法立即授予。相反地,交易 T2
必須等待交易 T1
釋放其在資料列 r
上的鎖定。
InnoDB
支援多重粒度鎖定,允許資料列鎖定和資料表鎖定同時存在。例如,像 LOCK TABLES ... WRITE
這樣的陳述式,會對指定的資料表取得獨佔鎖定(X
鎖定)。為了使多重粒度層級的鎖定更實用,InnoDB
使用意圖鎖定。意圖鎖定是資料表層級的鎖定,用於指示事務稍後需要在資料表中哪個資料列上使用哪種鎖定類型(共用或獨佔)。意圖鎖定有兩種型別:
例如,SELECT ... FOR SHARE
設定 IS
鎖定,而 SELECT ... FOR UPDATE
設定 IX
鎖定。
意圖鎖定協定如下:
在事務可以取得資料表中資料列上的共用鎖定之前,它必須先在資料表上取得
IS
鎖定或更強的鎖定。在事務可以取得資料表中資料列上的獨佔鎖定之前,它必須先在資料表上取得
IX
鎖定。
下表總結了資料表層級鎖定類型的相容性。
X |
IX |
S |
IS |
|
---|---|---|---|---|
X |
衝突 | 衝突 | 衝突 | 衝突 |
IX |
衝突 | 相容 | 衝突 | 相容 |
S |
衝突 | 衝突 | 相容 | 相容 |
IS |
衝突 | 相容 | 相容 | 相容 |
如果鎖定與現有鎖定相容,則會授予請求鎖定的事務;如果與現有鎖定衝突,則不會授予。事務會等待,直到釋放衝突的現有鎖定。如果鎖定請求與現有鎖定衝突,且因為會導致死鎖而無法授予,則會發生錯誤。
意圖鎖定不會封鎖任何內容,除非是完整的資料表請求(例如,LOCK TABLES ... WRITE
)。意圖鎖定的主要目的是表明有人正在鎖定資料列,或即將鎖定資料表中的資料列。
在 SHOW ENGINE INNODB STATUS
和 InnoDB 監視器的輸出中,意圖鎖定的事務資料會類似如下:
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
記錄鎖定是對索引記錄的鎖定。例如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
會防止任何其他事務插入、更新或刪除 t.c1
的值為 10
的資料列。
記錄鎖定總是會鎖定索引記錄,即使資料表未定義索引。在這種情況下,InnoDB
會建立一個隱藏的叢集索引,並使用此索引進行記錄鎖定。請參閱第 17.6.2.1 節「叢集和次要索引」。
在 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 ;;
間隙鎖定是對索引記錄之間間隙的鎖定,或是對第一個索引記錄之前或最後一個索引記錄之後間隙的鎖定。例如,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 可以判斷資料列是否符合 UPDATE
的 WHERE
條件。
下一個鍵鎖定是索引記錄的記錄鎖定和索引記錄之前的間隙鎖定的組合。
InnoDB
會以這樣的方式執行資料列層級鎖定:當它搜尋或掃描資料表索引時,它會在它遇到的索引記錄上設定共用或獨佔鎖定。因此,資料列層級鎖定實際上是索引記錄鎖定。索引記錄上的下一個鍵鎖定也會影響該索引記錄之前的「間隙」。也就是說,下一個鍵鎖定是索引記錄鎖定,再加上索引記錄之前的間隙鎖定。如果一個會話在索引中對記錄 R
持有共用或獨佔鎖定,則另一個會話無法在索引順序中緊接在 R
之前的間隙中插入新的索引記錄。
假設索引包含值 10、11、13 和 20。此索引可能存在的下一個鍵鎖定涵蓋以下間隔,其中圓括號表示排除間隔端點,而方括號表示包含端點
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
對於最後一個間隔,下一個鍵鎖定會鎖定索引中最大值之上的間隙,以及值高於索引中實際任何值的「上界」虛擬記錄。上界不是真實的索引記錄,因此,實際上,此下一個鍵鎖定只鎖定最大索引值之後的間隙。
依預設,InnoDB
會在 REPEATABLE READ
事務隔離層級中運作。在這種情況下,InnoDB
會將下一個鍵鎖定用於搜尋和索引掃描,這會防止幻像資料列(請參閱第 17.7.4 節「幻像資料列」)。
在 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
操作在插入資料列之前設定的一種間隙鎖定。此鎖定會發出在間隙中插入的意圖,這樣,如果多個事務不是在間隙中的相同位置插入,則插入到同一個索引間隙中的多個事務不需要彼此等待。假設有值為 4 和 7 的索引記錄。嘗試分別插入值 5 和 6 的獨立事務,在取得插入資料列的獨佔鎖定之前,會各自用插入意圖鎖定鎖定 4 和 7 之間的間隙,但因為資料列不衝突,因此彼此不會封鎖。
以下範例示範事務在取得插入記錄的獨佔鎖定之前取得插入意圖鎖定。範例涉及兩個用戶端 A 和 B。
用戶端 A 建立一個包含兩個索引記錄(90 和 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
鎖定是一種特殊資料表層級鎖定,由插入到具有 AUTO_INCREMENT
資料行的資料表的事務取得。在最簡單的情況下,如果一個事務正在將值插入到資料表中,則任何其他事務都必須等待才能將它們自己的值插入到該資料表中,以便第一個事務插入的資料列接收連續的主鍵值。
innodb_autoinc_lock_mode
變數會控制用於自動遞增鎖定的演算法。它允許您選擇如何在自動遞增值的可預測序列和插入操作的最大並行性之間進行權衡。
如需更多資訊,請參閱第 17.6.1.6 節「InnoDB 中的 AUTO_INCREMENT 處理」。
InnoDB
支援包含空間資料的資料行的 SPATIAL
索引(請參閱第 13.4.9 節「最佳化空間分析」)。
為了處理涉及 SPATIAL
索引的操作的鎖定,下一個鍵鎖定無法很好地支援 REPEATABLE READ
或 SERIALIZABLE
事務隔離層級。多維資料中沒有絕對排序概念,因此不清楚哪個是「下一個」鍵。
為了啟用對具有 SPATIAL
索引的表格的隔離等級支援,InnoDB
使用述詞鎖定。SPATIAL
索引包含最小邊界矩形 (MBR) 值,因此 InnoDB
會針對查詢所使用的 MBR 值設定述詞鎖定,以強制對索引進行一致讀取。其他交易無法插入或修改符合查詢條件的列。