鎖定讀取、UPDATE
或 DELETE
通常會在 SQL 語句處理過程中掃描的每個索引記錄上設定記錄鎖定。語句中是否有會排除該列的 WHERE
條件並不重要。InnoDB
不會記住確切的 WHERE
條件,而只知道掃描了哪些索引範圍。鎖定通常是next-key 鎖定,也會阻止在記錄正前方的 「間隙」中插入資料。但是,可以明確停用間隙鎖定,這會導致不使用 next-key 鎖定。如需詳細資訊,請參閱第 17.7.1 節「InnoDB 鎖定」。交易隔離等級也會影響設定的鎖定;請參閱第 17.7.2.1 節「交易隔離等級」。
如果在搜尋中使用次要索引,且要設定的索引記錄鎖定是獨佔的,InnoDB
也會擷取對應的叢集索引記錄,並在這些記錄上設定鎖定。
如果您的語句沒有合適的索引,且 MySQL 必須掃描整個資料表才能處理該語句,則資料表的每一列都會被鎖定,進而阻止其他使用者向資料表插入任何資料。建立良好的索引非常重要,這樣您的查詢就不會掃描超出必要的列。
InnoDB
設定特定類型的鎖定,如下所示。
SELECT ... FROM
是一個一致性讀取,讀取資料庫的快照,除非將交易隔離層級設定為SERIALIZABLE
,否則不會設定任何鎖定。對於SERIALIZABLE
層級,搜尋會在遇到的索引記錄上設定共用鍵鎖定 (shared next-key locks)。然而,對於使用唯一索引搜尋唯一列的鎖定列的語句,只需要索引記錄鎖定。使用唯一索引的
SELECT ... FOR UPDATE
和SELECT ... FOR SHARE
語句,會為掃描的列取得鎖定,並釋放不符合結果集條件的列的鎖定(例如,如果它們不符合WHERE
子句中給定的條件)。然而,在某些情況下,列可能不會立即解除鎖定,因為在查詢執行期間,結果列與其原始來源之間的關係會遺失。例如,在UNION
中,來自資料表的掃描(和鎖定)列可能會在評估它們是否符合結果集條件之前插入到暫存資料表。在這種情況下,暫存資料表中的列與原始資料表中的列的關係會遺失,並且後者列直到查詢執行結束才會解除鎖定。對於鎖定讀取(使用
FOR UPDATE
或FOR SHARE
的SELECT
)、UPDATE
和DELETE
語句,取得的鎖定取決於該語句是使用帶有唯一搜尋條件的唯一索引,還是範圍型搜尋條件。對於帶有唯一搜尋條件的唯一索引,
InnoDB
僅鎖定找到的索引記錄,而不是它之前的間隙。對於其他搜尋條件,以及非唯一索引,
InnoDB
會鎖定掃描的索引範圍,使用間隙鎖定或鍵鎖定來阻止其他工作階段將資料插入到該範圍所涵蓋的間隙中。有關間隙鎖定和鍵鎖定的資訊,請參閱第 17.7.1 節「InnoDB 鎖定」。
對於搜尋遇到的索引記錄,
SELECT ... FOR UPDATE
會阻止其他工作階段執行SELECT ... FOR SHARE
或在某些交易隔離層級中讀取。一致性讀取會忽略讀取視圖中存在的記錄上設定的任何鎖定。UPDATE ... WHERE ...
會在搜尋遇到的每個記錄上設定獨佔鍵鎖定。然而,對於使用唯一索引搜尋唯一列的鎖定列的語句,只需要索引記錄鎖定。當
UPDATE
修改叢集索引記錄時,會在受影響的次要索引記錄上取得隱式鎖定。UPDATE
操作也會在插入新的次要索引記錄之前執行重複檢查掃描以及插入新的次要索引記錄時,在受影響的次要索引記錄上取得共用鎖定。DELETE FROM ... WHERE ...
會在搜尋遇到的每個記錄上設定獨佔鍵鎖定。然而,對於使用唯一索引搜尋唯一列的鎖定列的語句,只需要索引記錄鎖定。INSERT
會在插入的列上設定獨佔鎖定。此鎖定是索引記錄鎖定,而不是鍵鎖定(也就是說,沒有間隙鎖定),並且不會阻止其他工作階段插入到插入列之前的間隙中。在插入列之前,會設定一種稱為插入意圖間隙鎖定的間隙鎖定。此鎖定會發出插入意圖的訊號,如此一來,如果多個交易插入到同一個索引間隙中,則它們不需要互相等待,除非它們插入到該間隙內的相同位置。假設索引記錄的值為 4 和 7。嘗試插入值 5 和 6 的個別交易會在取得插入列上的獨佔鎖定之前,使用插入意圖鎖定來鎖定 4 和 7 之間的間隙,但由於列不衝突,因此不會互相阻塞。
如果發生重複鍵錯誤,則會設定重複索引記錄上的共用鎖定。如果另一個工作階段已經具有獨佔鎖定,則如果有多個工作階段嘗試插入相同的列,則這種使用共用鎖定可能會導致死結。如果另一個工作階段刪除該列,則可能會發生這種情況。假設
InnoDB
資料表t1
具有以下結構CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
現在假設三個工作階段依序執行下列操作
工作階段 1
START TRANSACTION; INSERT INTO t1 VALUES(1);
工作階段 2
START TRANSACTION; INSERT INTO t1 VALUES(1);
工作階段 3
START TRANSACTION; INSERT INTO t1 VALUES(1);
工作階段 1
ROLLBACK;
工作階段 1 的第一個操作會取得該列的獨佔鎖定。工作階段 2 和 3 的操作都會導致重複鍵錯誤,並且它們都會要求該列的共用鎖定。當工作階段 1 回滾時,它會釋放該列的獨佔鎖定,並且會授與工作階段 2 和 3 排隊的共用鎖定要求。此時,工作階段 2 和 3 會發生死結:由於另一個工作階段持有的共用鎖定,因此它們都無法取得該列的獨佔鎖定。
如果資料表已經包含鍵值為 1 的列,並且三個工作階段依序執行下列操作,則會發生類似的情況
工作階段 1
START TRANSACTION; DELETE FROM t1 WHERE i = 1;
工作階段 2
START TRANSACTION; INSERT INTO t1 VALUES(1);
工作階段 3
START TRANSACTION; INSERT INTO t1 VALUES(1);
工作階段 1
COMMIT;
工作階段 1 的第一個操作會取得該列的獨佔鎖定。工作階段 2 和 3 的操作都會導致重複鍵錯誤,並且它們都會要求該列的共用鎖定。當工作階段 1 提交時,它會釋放該列的獨佔鎖定,並且會授與工作階段 2 和 3 排隊的共用鎖定要求。此時,工作階段 2 和 3 會發生死結:由於另一個工作階段持有的共用鎖定,因此它們都無法取得該列的獨佔鎖定。
INSERT ... ON DUPLICATE KEY UPDATE
與簡單的INSERT
的不同之處在於,當發生重複鍵錯誤時,會在要更新的列上放置獨佔鎖定,而不是共用鎖定。對於重複的主鍵值,會取得獨佔索引記錄鎖定。對於重複的唯一鍵值,會取得獨佔鍵鎖定。INSERT INTO T SELECT ... FROM S WHERE ...
會在插入到T
的每個列上設定獨佔索引記錄鎖定(沒有間隙鎖定)。如果交易隔離層級是READ COMMITTED
,則InnoDB
會將S
上的搜尋當做一致性讀取(沒有鎖定)。否則,InnoDB
會在S
中的列上設定共用鍵鎖定。InnoDB
必須在後一種情況下設定鎖定:在使用以語句為基礎的二進位記錄檔進行向前復原期間,每個 SQL 語句都必須以與其最初執行方式完全相同的方式執行。CREATE TABLE ... SELECT ...
會使用共用鍵鎖定或作為一致性讀取來執行SELECT
,如同INSERT ... SELECT
一樣。當在建構
REPLACE INTO t SELECT ... FROM s WHERE ...
或UPDATE t ... WHERE col IN (SELECT ... FROM s ...)
中使用SELECT
時,InnoDB
會在資料表s
中的列上設定共用鍵鎖定。InnoDB
會在初始化資料表上先前指定的AUTO_INCREMENT
資料行時,在與AUTO_INCREMENT
資料行相關聯的索引結尾設定獨佔鎖定。使用
innodb_autoinc_lock_mode=0
,InnoDB
會使用特殊的AUTO-INC
資料表鎖定模式,其中在存取自動遞增計數器時,會取得鎖定並將其保留到目前 SQL 語句結束(而不是整個交易結束)。當持有AUTO-INC
資料表鎖定時,其他用戶端無法插入到資料表中。對於使用innodb_autoinc_lock_mode=1
的「大量插入」,也會發生相同的行為。資料表層級的AUTO-INC
鎖定不會與innodb_autoinc_lock_mode=2
一起使用。如需更多資訊,請參閱第 17.6.1.6 節「InnoDB 中的 AUTO_INCREMENT 處理」。InnoDB
會擷取先前初始化的AUTO_INCREMENT
資料行的值,而不設定任何鎖定。如果在資料表上定義了
FOREIGN KEY
條件約束,則任何需要檢查條件約束條件的插入、更新或刪除都會在它查看以檢查條件約束的記錄上設定共用記錄層級鎖定。InnoDB
也會在條件約束失敗的情況下設定這些鎖定。LOCK TABLES
會設定資料表鎖定,但是由高於InnoDB
層的 MySQL 層設定這些鎖定。如果innodb_table_locks = 1
(預設值) 且autocommit = 0
,則InnoDB
會感知資料表鎖定,並且高於InnoDB
的 MySQL 層會知道資料列層級鎖定。否則,
InnoDB
的自動死結偵測無法偵測到涉及此類資料表鎖定的死結。此外,由於在這種情況下,高於的 MySQL 層不知道資料列層級鎖定,因此可能會在另一個工作階段目前具有資料列層級鎖定的資料表上取得資料表鎖定。但是,這不會危害交易完整性,如第 17.7.5.2 節「死結偵測」中所述。如果
innodb_table_locks=1
(預設值),則LOCK TABLES
會在每個資料表上取得兩個鎖定。除了 MySQL 層的資料表鎖定之外,它還會取得一個InnoDB
資料表鎖定。若要避免取得InnoDB
資料表鎖定,請設定innodb_table_locks=0
。如果未取得InnoDB
資料表鎖定,即使資料表的某些記錄正被其他交易鎖定,LOCK TABLES
仍會完成。在 MySQL 9.0 中,
innodb_table_locks=0
對於使用LOCK TABLES ... WRITE
明確鎖定的資料表沒有作用。它對於透過LOCK TABLES ... WRITE
隱式(例如,透過觸發器)或透過LOCK TABLES ... READ
鎖定以進行讀取或寫入的資料表有作用。當交易提交或中止時,交易持有的所有
InnoDB
鎖定都會被釋放。因此,在autocommit=1
模式下對InnoDB
資料表調用LOCK TABLES
沒有太大意義,因為取得的InnoDB
資料表鎖定會立即被釋放。您無法在交易過程中鎖定其他資料表,因為
LOCK TABLES
會執行隱式的COMMIT
和UNLOCK TABLES
。