天天看點

MySQL metalock的一些技巧(寫大于讀的案例,以及獲得鎖的順序)

前言:中繼資料鎖不是鎖定資料,而是鎖定描述資料的中繼資料資訊。就像很多裝修勞工(工作線程)在室内(對象上)裝修(操作),不能有其他勞工(線程)把屋子拆了(表删除了)。

MySQL 為了資料一緻性使用中繼資料鎖來管理并發通路資料庫中的對象。中繼資料鎖不僅僅作用于表上,同時對存儲程式(schemas,procedure,function,triggers,events)以及表空間都适用。

譯者廢話:也就是說,為防止一個線程在插入一條記錄,另一個線程删了表。是以在第一個線程在寫入時,還要有中繼資料鎖。

性能資料庫(Performance Schema)中的 metadata_lock 表記錄了關于中繼資料鎖的資訊。其中可以看到哪個會話持有鎖、因為等待鎖被堵塞等等。關于這個表的詳細資訊請看。

https://dev.mysql.com/doc/refman/5.7/en/metadata-locks-table.html

中繼資料鎖定會涉及一些開銷,随着查詢量的增加而增加。 中繼資料争用會增加,多個查詢嘗試通路相同對象。

中繼資料鎖定不是table definition cache的替代品,他的互斥鎖(mutexes)和鎖(locks)與LOCK_open互斥鎖不同。 以下讨論提供了有關中繼資料鎖定如何工作的一些資訊。

  • 獲得中繼資料鎖
  • 釋放中繼資料鎖

獲得中繼資料鎖

如果對于一個給定的鎖有很多的等待者,首先會滿足權重最高的鎖請求,并且和max_write_lock_count有關。寫鎖請求的權重高于讀鎖請求。 但是,如果将max_write_lock_count設定為某個較低的值(例如,10),則如果已經傳遞了讀取鎖定請求以支援10個寫入鎖定請求,則讀取鎖定請求可能優先于挂起的寫入鎖定請求。 通常不會發生此行為,因為預設情況下max_write_lock_count具有非常大的值。筆者廢話:也就說讀鎖要讓着寫鎖。這在資料庫内部随處可見。寫不能被餓死在檔案系統也可展現呢。

語句逐個擷取中繼資料鎖,而不是同時擷取,并在此過程中執行死鎖檢測。

DML語句獲得鎖的順序和語句中提及到的表順序一緻。

DDL語句,LOCK TABLES和其他類似語句嘗試通過按名稱順序擷取顯式命名表上的鎖來減少并發DDL語句之間可能出現的死鎖數。 對于隐式使用的表,可能以不同的順序擷取鎖,比如外鍵關系的表也會被鎖。筆者廢話:一定要注意其中的名稱順序,實際上就是名稱字典序。并非SQL語句中提及到的順序。

比如說 RENAME TABLE 是一個DDL語句,它會根據順序來擷取鎖。a c d

RENAME TABLE tbla TO tbld, tblc TO tbla;      

如下獲得鎖順序: a b c

RENAME TABLE tbla TO tblb, tblc TO tbla;      

兩個語句按順序擷取tbla和tblc上的鎖,但是在tblc之前或之後是否擷取,對剩餘表名的鎖定不同。(筆者廢話:這就是廢話吧)

當多個事務同時執行時,中繼資料鎖擷取順序可以在操作結果上産生差異,如下例所示。

從兩個具有相同結構的表x和x_new開始。 三個用戶端發出涉及這些表的語句:

session1 LOCK TABLE x WRITE, x_new WRITE;      

這個語句按x 、x_new 請求并獲得鎖。

session2 INSERT INTO x VALUES(1);      

這個會話會被堵塞,因為它在等待x 的 寫鎖。

session3 RENAME TABLE x TO x_old, x_new TO x;      

這個語句請求獲得排他鎖(exclusive lock),順序是 x, x_new, x_old 。但是因為要擷取x 寫鎖而被堵塞

session1 UNLOCK TABLES;      

釋放x 、x_new 的寫鎖,此時 session3 的排它鎖(exclusive lock)請求比session2 的write lock有更高的優先級。索引執行順序是先執行session3,然後才是session2的。(筆者注:這裡的session3是要對metadata進行更改,是以需要的是一個排他鎖,而session2對x表進行插入,并不涉及更改x的metadata,是以隻是一個讀鎖,讀鎖是可以被其他讀鎖共享,也就是說不會影響其他插入。至于文檔為什麼寫時write lock。不得而知。由于上述session2 ,session3 的兩個操作都是瞬時完成,不好判斷誰先誰後,讀者可以根據binlog生成的順序來驗證。其實在官方的這個案例中,自帶了有趣的驗證,因為由于是先改名後插入,是以新插入的資料,實際上是在改名後的表中。)

mysql> SELECT * FROM x;       
+------+ | i    |       
+------+ |    1 | +------+       
mysql> SELECT * FROM x_old;       
Empty set (0.01 sec)      

以上證明了寫鎖高于讀鎖的請求

=================

現在來看以下案例。與上一同證明了DDL語句擷取鎖的順序是字典序的

x 和 new_x 有相同的結構。再一次重複上述語句。

session1 LOCK TABLE x WRITE, new_x WRITE;      
session2 INSERT INTO x VALUES(1);       
session3 RENAME TABLE x TO old_x, new_x TO x;       
session1 UNLOCK TABLES;      

這時候,猜猜這個資料插入到哪裡了。session3獲得鎖的順序是new_x, x, old_x。而session2需要x鎖。是以session2先插入記錄到x中,然後x改名為old_x 。記錄是在old_x中。

中繼資料鎖的釋放

為確定事務串行化,MySQL不得允許一個會話在另一個會話中未完成的顯式或隐式啟動的事務中使用的表上,執行資料定義語言(DDL)語句。伺服器通過擷取事務中使用的表的中繼資料鎖并延遲釋放這些鎖直到事務結束來實作此目的。 表上的中繼資料鎖可防止更改表的結構。 這種鎖定方法的含義是,在事務結束之前,其他會話不能在DDL語句中使用一個會話中的事務正在使用的表。(譯者廢話:也就說,不允許在對一張表做操作過程中,表的結構被其他線程改了。這是很顯然需要保護的)

這個規則不僅僅适用于事務表,對非事務表同樣适用。假設一個會話以一個事務表 t 和一個非事務表 啟動事務。如下:

START TRANSACTION; SELECT * FROM t; SELECT * FROM nt;      

該會話持有t、nt的中繼資料鎖,直到事務結束。如果有其他線程嘗試在這兩個表上做一個DDL語句(exclusive lock)或者write lock操作。比如,下面的語句都會被堵塞

DROP TABLE t; ALTER TABLE t ...; DROP TABLE nt; ALTER TABLE nt ...; LOCK TABLE t ... WRITE;      

同樣的行為适用于LOCK TABLES ... READ。 也就是說,更新任何表(事務性或非事務性)的顯式或隐式啟動事務将被阻塞。

如果服務擷取文法有效但在執行期間失敗的語句的中繼資料鎖,則它不會提前釋放鎖(“譯者注:比如插入沖突”)。 鎖定釋放仍然延遲到事務結束,因為失敗的語句被寫入二進制日志,并且鎖定保護日志一緻性。

在autocommit = true 的情況下,語句執行完,中繼資料鎖就釋放了。

在prepare語句後,即使在多語句事務中進行prepare,也會釋放PREPARE語句期間擷取的中繼資料鎖。