深入理解MySQL的MDL元資料鎖
前言
好久沒更新,主要是因為Inside君最近沉迷於一部動畫片——《新葫蘆娃兄弟》。終於抽得閒,完成了本篇關於MySQLMDL鎖的深入分析與介紹。雖然之前有很多小夥伴分析過,但總感覺少了點什麼,故花了點時間翻看了下原始碼。Inside君或許不是最牛掰的核心開發人員,但自認為應該是業界最會講故事的碼農,希望本篇能做到通俗易懂,因為MDL鎖其實並不好理解。如果同學們還有問題,也可以直接看原始碼檔案mdl.cc。
MDL鎖與實現
MySQL5.5版本引入了MDL鎖(metadatalock),用於解決或者保證DDL操作與DML等操作之間的一致性。例如下面的這種情形:
若沒有MDL鎖的保護,則事務2可以直接執行DDL操作,並且導致事務1出錯,5.1版本即是如此。5.5版本加入MDL鎖就在於保護這種情況的發生,由於事務1開啟了查詢,那麼獲得了MDL鎖,鎖的模式為SHARED_READ,事務2要執行DDL,則需獲得EXCLUSIVE鎖,兩者互斥,所以事務2需要等待。
InnoDB層已經有了IS、IX這樣的意向鎖,有同學覺得可以用來實現上述例子的併發控制。但由於MySQL是Server-Engine架構,所以MDL鎖是在Server中實現。另外,MDL鎖還能實現其他粒度級別的鎖,比如全域性鎖、庫級別的鎖、表空間級別的鎖,這是InnoDB儲存引擎層不能直接實現的鎖。
但與InnoDB鎖的實現一樣,MDL鎖也是類似對一顆樹的各個物件從上至下進行加鎖(對樹進行加鎖具體見:《MySQL技術內幕:InnoDB儲存引擎》)。但是MDL鎖物件的層次更多,簡單來看有如下的層次:
上圖中顯示了最常見的4種MDL鎖的物件,並且註明了常見的SQL語句會觸發鎖。與InnoDB層類似的是,某些型別的MDL鎖會從上往下一層層進行加鎖。比如LOCKTABLE … WRITE這樣的SQL語句,其首先會對GLOBAL級別加INTENTION_EXCLUSIVE鎖,再對SCHEMA級別加INTENTION_EXCLUSIVE鎖,最後對TABLE級別加SHARED_NO_READ_WRITE鎖。
這裡最令人意外的是還有COMMIT物件層次的鎖,其實這主要用於XA事務中。比如分散式事務已經PREPARE成功,但是在XACOMMIT之前有其他會話執行了FLUSHTABLES WITH READ LOCK這樣的操作,那麼分散式事務的提交就需要等待。
除了上圖標註的物件,其實還有TABLESPACE、FUNCTION、PROCEDURE、EVENT等其他物件型別,其實都是為了進行併發控制。只是這些在MySQL資料庫中都不常用,故不再贅述(當然也是為了偷懶)。
目前MDL有如下鎖模式,鎖之間的相容性可見原始碼mdl.cc:
MDL鎖的效能與併發改進
講到這同學們會發現MDL鎖的開銷並不比InnoDB層的行鎖要小,而且這可能是一個更為密集的併發瓶頸。MySQL5.6和5.5版本通常通過調整如下兩個引數來進行併發調優:
- metadata_locks_cache_size: MDL鎖的快取大小
- metadata_locks_hash_instances:通過分片來提高併發度,與InnoDB AHI類似
MySQL 5.7 MDL鎖的最大改進之處在於將MDL鎖的機制通過lock free演算法來實現,從而提高了在多核併發下資料庫的整體效能提升。
MDL鎖的診斷
MySQL 5.7版本之前並沒有提供一個方便的途徑來檢視MDL鎖,github上有一名為mysql-plugin-mdl-info的專案,通過外掛的方式來檢視,非常有想法的實現,大讚。好在官方也意識到了這個問題,於是在MySQL5.7中的performance_schea庫下新增了一張表metadata_locks,用其來檢視MDL鎖那是相當的方便:
不過預設PS並沒有開啟此功能,需要手工將wait/lock/metadata/sql/mdl監控給開啟:
([email protected]) [performance_schema]> update setup_instruments
-> set enabled=’yes’,timed=’yes’ where name = ‘wait/lock/metadata/sql/mddl’;
Query OK, 0 rows affected (0.00 sec)
文:姜承堯
原文出處:InsideMySQL