MySQL 使用中繼資料鎖定來管理對資料庫物件的並行存取,並確保資料一致性。中繼資料鎖定不僅適用於資料表,也適用於結構描述、預存程式(程序、函式、觸發程序、排程事件)、資料表空間、使用 GET_LOCK()
函式取得的使用者鎖定(請參閱 第 14.14 節,「鎖定函式」),以及在 第 7.6.9.1 節,「鎖定服務」中描述的鎖定服務取得的鎖定。
效能結構描述 metadata_locks
資料表會公開中繼資料鎖定資訊,這對於查看哪些工作階段持有鎖定、正在封鎖等待鎖定等情況很有用。如需詳細資訊,請參閱第 29.12.13.3 節,「metadata_locks 資料表」。
中繼資料鎖定確實會產生一些額外負擔,而且隨著查詢量的增加而增加。當中繼資料衝突增加時,多個查詢會嘗試存取相同的物件。
中繼資料鎖定並不能取代資料表定義快取,而且其互斥鎖和鎖定與 LOCK_open
互斥鎖不同。以下討論提供一些關於中繼資料鎖定如何運作的資訊。
如果有多個等待者要求特定鎖定,則會先滿足優先權最高的鎖定要求,但與 max_write_lock_count
系統變數相關的例外情況除外。寫入鎖定要求具有比讀取鎖定要求更高的優先權。但是,如果將 max_write_lock_count
設定為較低的值(例如 10),如果讀取鎖定要求已經被優先考慮 10 個寫入鎖定要求,則可能會優先選擇讀取鎖定要求,而不是等待中的寫入鎖定要求。通常,不會發生這種行為,因為 max_write_lock_count
預設具有非常大的值。
陳述式會逐一取得中繼資料鎖定,而不是同時取得,並在此過程中執行死鎖偵測。
DML 陳述式通常會按照陳述式中提及資料表的順序取得鎖定。
DDL 陳述式、LOCK TABLES
和其他類似陳述式會嘗試透過依名稱順序取得明確命名資料表的鎖定,來減少並行 DDL 陳述式之間可能發生的死鎖數量。對於隱含使用的資料表(例如外鍵關聯中的資料表,也必須鎖定),可能會以不同的順序取得鎖定。
例如,RENAME TABLE
是一個依名稱順序取得鎖定的 DDL 陳述式
此
RENAME TABLE
陳述式會將tbla
重新命名為其他名稱,並將tblc
重新命名為tbla
RENAME TABLE tbla TO tbld, tblc TO tbla;
該語句依序在
tbla
、tblc
和tbld
上取得中繼資料鎖定(因為tbld
在名稱順序上接在tblc
之後)。這個稍微不同的語句也會將
tbla
重新命名為其他名稱,並將tblc
重新命名為tbla
。RENAME TABLE tbla TO tblb, tblc TO tbla;
在這種情況下,該語句依序在
tbla
、tblb
和tblc
上取得中繼資料鎖定(因為tblb
在名稱順序上先於tblc
)。
這兩個語句都會依序在 tbla
和 tblc
上取得鎖定,但差別在於剩餘的表格名稱上的鎖定是在 tblc
之前還是之後取得。
當多個交易同時執行時,中繼資料鎖定的取得順序可能會影響操作結果,如下面的範例所示。
從兩個具有相同結構的表格 x
和 x_new
開始。三個用戶端發出包含這些表格的語句。
用戶端 1
LOCK TABLE x WRITE, x_new WRITE;
該語句依名稱順序在 x
和 x_new
上請求並取得寫入鎖定。
用戶端 2
INSERT INTO x VALUES(1);
該語句請求並封鎖等待 x
上的寫入鎖定。
用戶端 3
RENAME TABLE x TO x_old, x_new TO x;
該語句依名稱順序在 x
、x_new
和 x_old
上請求獨佔鎖定,但封鎖等待 x
上的鎖定。
用戶端 1
UNLOCK TABLES;
該語句釋放 x
和 x_new
上的寫入鎖定。用戶端 3 對 x
的獨佔鎖定請求優先順序高於用戶端 2 的寫入鎖定請求,因此用戶端 3 取得其在 x
上的鎖定,然後也取得 x_new
和 x_old
上的鎖定,執行重新命名,並釋放其鎖定。用戶端 2 接著取得其在 x
上的鎖定,執行插入,並釋放其鎖定。
鎖定取得順序導致 RENAME TABLE
在 INSERT
之前執行。執行插入的 x
是當用戶端 2 發出插入時名為 x_new
的表格,並由用戶端 3 重新命名為 x
。
mysql> SELECT * FROM x;
+------+
| i |
+------+
| 1 |
+------+
mysql> SELECT * FROM x_old;
Empty set (0.01 sec)
現在改為從名為 x
和 new_x
且具有相同結構的表格開始。同樣地,三個用戶端發出包含這些表格的語句。
用戶端 1
LOCK TABLE x WRITE, new_x WRITE;
該語句依名稱順序在 new_x
和 x
上請求並取得寫入鎖定。
用戶端 2
INSERT INTO x VALUES(1);
該語句請求並封鎖等待 x
上的寫入鎖定。
用戶端 3
RENAME TABLE x TO old_x, new_x TO x;
該語句依名稱順序在 new_x
、old_x
和 x
上請求獨佔鎖定,但封鎖等待 new_x
上的鎖定。
用戶端 1
UNLOCK TABLES;
該語句釋放 x
和 new_x
上的寫入鎖定。對於 x
,唯一的待處理請求來自用戶端 2,因此用戶端 2 取得其鎖定、執行插入,並釋放該鎖定。對於 new_x
,唯一的待處理請求來自用戶端 3,允許用戶端 3 取得該鎖定(以及 old_x
上的鎖定)。重新命名操作仍然會封鎖等待 x
上的鎖定,直到用戶端 2 完成插入並釋放其鎖定。然後用戶端 3 取得 x
上的鎖定,執行重新命名,並釋放其鎖定。
在這種情況下,鎖定取得順序導致 INSERT
在 RENAME TABLE
之前執行。執行插入的 x
是原始的 x
,現在由重新命名操作重新命名為 old_x
。
mysql> SELECT * FROM x;
Empty set (0.01 sec)
mysql> SELECT * FROM old_x;
+------+
| i |
+------+
| 1 |
+------+
如果並行語句中的鎖定取得順序會影響應用程式的操作結果,如上述範例所示,您可以調整表格名稱以影響鎖定取得的順序。
中繼資料鎖定會視需要擴展到受外鍵約束相關聯的表格,以防止相關表格上同時執行衝突的 DML 和 DDL 操作。更新父表格時,會在更新外鍵中繼資料時,在子表格上取得中繼資料鎖定。外鍵中繼資料由子表格擁有。
為了確保交易可序列化,伺服器不得允許一個工作階段在另一個工作階段中未完成的明確或隱含啟動的交易中使用的表格上執行資料定義語言 (DDL) 語句。伺服器透過在交易中使用的表格上取得中繼資料鎖定,並將這些鎖定的釋放延遲到交易結束時,來實現此目的。表格上的中繼資料鎖定可防止表格結構的變更。這種鎖定方法意味著,在一個工作階段內的交易使用的表格,在交易結束之前,其他工作階段無法在 DDL 語句中使用該表格。
此原則不僅適用於交易表格,也適用於非交易表格。假設一個工作階段開始一個交易,該交易使用交易表格 t
和非交易表格 nt
,如下所示:
START TRANSACTION;
SELECT * FROM t;
SELECT * FROM nt;
伺服器會保持 t
和 nt
上的中繼資料鎖定,直到交易結束。如果另一個工作階段嘗試對任一表格執行 DDL 或寫入鎖定操作,則會封鎖,直到在交易結束時釋放中繼資料鎖定。例如,如果第二個工作階段嘗試執行以下任何操作,則會封鎖:
DROP TABLE t;
ALTER TABLE t ...;
DROP TABLE nt;
ALTER TABLE nt ...;
LOCK TABLE t ... WRITE;
相同的行為適用於 LOCK TABLES ... READ
。也就是說,明確或隱含啟動的交易,如果更新任何表格(交易或非交易),則會封鎖,並且會被該表格的 LOCK TABLES ... READ
封鎖。
如果伺服器為語法上有效但在執行期間失敗的語句取得中繼資料鎖定,則不會提前釋放鎖定。鎖定釋放仍然會延遲到交易結束,因為失敗的語句會寫入二進位日誌,而鎖定可保護日誌的一致性。
在自動認可模式中,每個語句實際上都是一個完整的交易,因此為該語句取得的中繼資料鎖定只會保持到該語句結束。
在 PREPARE
語句期間取得的中繼資料鎖定,一旦語句準備好就會釋放,即使準備發生在多語句交易中也一樣。
對於 PREPARED
狀態的 XA 交易,中繼資料鎖定會在用戶端斷線和伺服器重新啟動時保持,直到執行 XA COMMIT
或 XA ROLLBACK
。