MySQL 使用 metadata 鎖定來管理對資料庫物件的並行存取,並確保資料一致性。Metadata 鎖定不僅適用於資料表,也適用於結構描述、儲存的程式 (程序、函式、觸發程序、排程事件)、資料表空間、使用 GET_LOCK()
函式取得的使用者鎖定 (請參閱 第 14.14 節 「鎖定函式」),以及使用 第 7.6.9.1 節 「鎖定服務」 中所述的鎖定服務取得的鎖定。
Performance Schema metadata_locks
資料表會公開 metadata 鎖定資訊,這對於查看哪些工作階段持有鎖定、正被封鎖等待鎖定等等很有用。如需詳細資訊,請參閱 第 29.12.13.3 節 「metadata_locks 資料表」。
Metadata 鎖定確實會產生一些額外負荷,並且會隨著查詢量增加而增加。當多個查詢嘗試存取相同的物件時,Metadata 競爭會增加。
Metadata 鎖定並不是資料表定義快取的替代品,而且其 mutex 和鎖定與 LOCK_open
mutex 不同。以下討論提供一些關於 metadata 鎖定如何運作的資訊。
如果有多個等待相同鎖定的等待者,則優先順序最高的鎖定請求會先得到滿足,但與 max_write_lock_count
系統變數相關的例外情況除外。寫入鎖定請求的優先順序高於讀取鎖定請求。但是,如果 max_write_lock_count
設定為較低的值 (例如,10),則如果讀取鎖定請求已經為了 10 個寫入鎖定請求而被略過,則可能會優先選擇讀取鎖定請求而不是等待中的寫入鎖定請求。通常不會發生這種情況,因為 max_write_lock_count
預設值非常大。
敘述會依序取得 metadata 鎖定,而不是同時取得,並在此過程中執行死鎖偵測。
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
。