mysql鎖系列之MDL元數據鎖之一

基礎材料:

centos7.5 mysql 5.7.24


當mysql運行一條SQL語句時,在你預期的時間內,沒有完成時,我們都會登陸到mysql數據庫上想查看是不是出了什麼問題,通常會使用的一個命令就是 show processlist,看看有哪些session,這些session在做什麼事情。就從這個命令開始,顯示如下:

 圖中看到了顯示了幾處信息:

 id:爲session_id,也就是processlist_id

 user:該session使用什麼用戶登陸的mysql數據庫

 host:客戶端登陸的ip地址(這裏我都是本地登陸的)

 db:連接了哪個數據庫(這裏我只是連接上了數據庫,並沒有其他操作,所以都是NULL)

 command:當前session執行命令的類型

 Time:處於當前命令類型持續的時間

 State:當前命令類型的狀態

 Info:具體命令信息


瞭解上面內容的含義後,初始化一下測試環境,模擬MDL鎖等待,各個session按順序執行命令如下:

id 41 id 42 id 43 id 44 id 45
  begin; begin; alter table testok add z varchar(10) not Null; select * from testok;
  select * from testok limit 1; select * from testok limit 1;    

 

 

 

 

說明:測試環境有一個test庫,裏面有一張表testok(innodb),裏面有幾行數據。在id 41沒有執行任何命令,該session用於查看結果。id 42 開啓一個事務,接着執行了一條查詢語句,緊接着id 43 開啓一個事務,也執行了一條查詢語句。id 44爲該表添加一個字段,id 45查詢testok表。

這時在id 41 執行show processlist,結果如下:

 

可以看到與之前的一些變化,其中id 44 45的state變成了 Waiting for table metadata lock,即等待元數據鎖,後面的Info即爲上面執行命令。


這裏簡單解釋一下產生元數據鎖的原因,元數據鎖是server層的鎖,表級鎖,主要用於隔離DML和DDL操作之間的干擾。每執行一條DML、DDL語句時都會申請MDL鎖,DML操作需要MDL讀鎖,DDL操作需要MDL寫鎖(MDL加鎖過程是系統自動控制,無法直接干預,讀讀共享,讀寫互斥,寫寫互斥),申請MDL鎖的操作會形成一個隊列,隊列中寫鎖獲取優先級高於讀鎖。一旦出現寫鎖等待,不但當前操作會被阻塞,同時還會阻塞後續該表的所有操作。事務一旦申請到MDL鎖後,直到事務執行完纔會將鎖釋放。(這裏有種特殊情況如果事務中包含DDL操作,mysql會在DDL操作語句執行後,隱式提交commit,以保證該DDL語句操作作爲一個單獨的事務存在,同時也保證元數據排他鎖的釋放,例如id 44的語句改爲<begin;alter table testok add z varchar(10) not Null;select * from testok;>,此時一旦alter語句執行完成會馬上提交事務(autocommit=1),後面的select就在本次事務之外,其執行完成後不會持有讀鎖)

下表爲總結的表級元數據鎖信息:

1、SHARED_UPGRADABLE本身爲讀鎖但有些特殊,所以列表裏把它的優先級設置爲0.

      一、並不受隊列中的寫鎖等待而阻塞,只和當前持鎖的session比對,當前持鎖session爲排他鎖X,則等待,反之獲得鎖

      二、爲了保證一張表同時只有一個DDL操作進行,SHARED_UPGRADABLE之間是互斥的,即一個時刻只有一個SHARED_UPGRADABLE是GRANTED狀態,其餘是被阻塞。

2、EXCLUSIVE、SHARED_NO_READ_WRITE級別相同,在隊列中排隊,先進先出。

3、SHARED_WRITE與SHARED_READ兼容,但SHARED_WRITE優先級高於SHARED_READ_ONLY且不兼容

4、SHARED_READ與SHARED_WRITE和SHARED_READ_ONLY分別兼容。即如果前面的持鎖類型爲SHARED_WRITE,則可以獲得鎖。如果前面持鎖類型爲SHARED_READ_ONLY,也可以獲得鎖。

5、SHARED_READ_ONLY優先級最低,主要是因爲被SHARED_WRITE互斥,但如果只有SHARED_READ則他們的優先級是兼容的。

如果看元數據鎖名字比較懵,可以查看mysql意向鎖的兼容互斥表。

名稱 類型 優先級 說明
SHARED_UPGRADABLE 共享升級鎖 0 一般在執行DDL時在on-line情況下會產生該鎖
EXCLUSIVE 排他鎖X 1 一般在執行DDL時會產生該鎖
SHARED_NO_READ_WRITE 排他鎖X 1 執行lock tables xxx write產生該鎖
SHARED_WRITE 意向排他鎖IX 2 一般執行DML的更新語句 或 select ... for update產生該鎖
SHARED_READ 意向共享鎖IS 2.5 select ... lock in share mode產生該鎖(8.0版本以後使用select...for share)
SHARED_READ_ONLY 共享鎖S 3 執行lock tables xxx read產生該鎖

所以在對錶做DDL操作時,需要注意元數據鎖的情況,避免事務長期持有元數據鎖或在長事務執行時進行DDL操作,這樣很容易阻塞該表的後續操作,而如果客戶端有重試機制時,隨着重試次數增多可能會打滿數據庫的連接,從而影響整個數據庫。當然在目前版本中已經有了online DDL的支持,優化DDL操作時進行鎖降級成讀鎖,在DDL過程中減小影響,但online DDL第一步仍然是需要獲得元數據寫鎖,如果在第一步就卡住,結果和本次模擬操作是一樣的,會影響後續操作。


所以上面語句執行完成後,id 42開啓了事務執行了查詢,此時先申請到了MDL讀鎖(也就是意向共享鎖IS),並持有該鎖,因爲並沒有提交。id 43開啓了事務執行了查詢,此時也可以申請到MDL讀鎖,所以他的查詢語句是可以正常執行的。id 44對錶結構進行了修改,需要申請MDL寫鎖,此時與id 42和 id 43互斥,無法得到寫鎖,所以他會被卡住,進入鎖等待。而id 45只是查詢該表,申請MDL讀鎖即可,與id 42和 id 43並不衝突,但是排在他前面id 44是寫鎖等待,而它只能排在id 44後面得到鎖,所以被互斥,進入鎖等待。

但在實際環境中,我們從上圖能獲得的信息是id 44、id 45進入了鎖等待,但是並不知道是哪個session持有這個元數據鎖。這時我們可能需要performance_schema庫下的四張表metadata_locks、threads、events_statements_current及events_statements_history。

events_statements_current記錄了所有在線session執行的最後一條語句

events_statements_history記錄了所有在線session執行語句的歷史記錄(默認每個session記錄10條數據,由全局參數performance_schema_events_statements_history_size決定,如果session下線則相關記錄會自動被刪除)

threads表用來關聯processlist_id及thread_id

metadata_locks表記錄了元數據鎖的信息

在開始之前需要開啓metadata_locks的監控,執行如下語句:

mysql > UPDATE performance_schema.setup_instruments set enabled='YES' WHERE NAME = 'wait/lock/metadata/sql/mdl';


現在開始查找具體是哪個session持有該鎖,打開監控後首先要查找的表是 metadata_locks,結果如下:

觀察輸出:

第1行:表示thread_id 68 持有testok的元數據讀鎖(lock_status爲granted)

第2行:表示thread_id 69 持有testok的元數據讀鎖(lock_status爲granted)

第3-6行:表示thread_id 70分別加了全局意向排他鎖,test數據庫的意向排他鎖,表空間testok的意向排他鎖,這是由於我們在執行alter table命令時需要額外加的鎖,元數據鎖也是從樹狀態結構一級一級加下來的,全局>數據庫>表空間>表本身,保證每一個層級的操作權限。爲什麼DDL操作需要加這些鎖,試想以下情況,執行flush tables with read lock去做一些備份的事情,如果此時執行alter table而不判斷全局層的鎖信息,會直接在表上嘗試去加元數據排他鎖(寫),然而發現上面已經有了共享鎖(讀),則直接進入了鎖等待,根據上面說的情況,會阻塞後面的查詢請求。而首先嚐試在全局層加意向排他鎖時,發現無法獲得,則在全局層就報錯了,回退。而不影響表的操作。其中第5行在testok表上加了shared_upgradable共享升級鎖,這個和mysql的online DDL特性有關(如想了解可以自行查找相關信息,後續也會寫一下)。

第7行:表示thread_id 70在testok表上申請了元數據排他鎖,但是pending了,就是上面看到的鎖等待。

第8行:表示thread_id 71在testok表上申請了元數據共享鎖,但是也pending了。

第9行:是查詢metadata_locks表產生的元數據共享鎖,忽略。


經過以上的查找,我們瞭解了目前thread_id 68 69持有testok的元數據讀鎖,而thread_id 70 71在等待這個讀鎖,這時已經找到了“帶着面具”的元兇了,接下來需要把面具撕下來,看看它到底是誰。

查找threads表,以其中一條數據爲例:

通過該表我們可以將thread_id與processlist_id聯繫起來,也可以與thread_os_id聯繫起來(在操作系統中執行top -H -p [mysql_pid])

至此就找到了thread 68 69 對應的processlist id 42 43就是持有該鎖的session.

同時我們可以通過events_statements_current、events_statements_history兩張表查看這兩個session執行哪些語句導致了鎖沒有釋放。

首先是events_statements_current,可以看出thread 68 69最後執行的語句正是上面列表中的select語句,但這並不是不釋放鎖的原因,前面已經說了事務完成後會將鎖釋放掉

所以還需要查看events_statements_history,觀察下表,以thread_id 69爲例,按照event_id排序,發現select的上一個事件是begin開啓了事務,但是並沒有commit,至此就回溯到了根本原因。


接下來就是如何處理:

1.如果session的客戶端還健在的話,可以直接執行commit

2.如果session的客戶端已經掛了,執行kill processlist_id

3.調整lock_wait_timeout鎖超時等待時間,讓超時的事務自動回滾。(該值默認值是一年....) 


最後還要補充一個相關問題,id 44和id 45在本次實驗中肉眼觀察是一起完成的,實際上他們即不是一起完成的,也不是id 44的寫操作先完成,在去執行id 45的查詢操作。而是id 45的查詢操作先完成,id 44的DDL操作後完成。

可以實驗一下,如果把id45執行的語句變成與id 42 43相同會出現什麼情況?

這個問題與online DDL有關 ,將在《mysql鎖系列之MDL元數據鎖之三》中進行說明

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章