Mysql解決USE DB堵塞詳解
遇到故障,我們往往想的是如何解決這個故障,而不是從故障的根本去思考出現這個故障的原因?這樣的結果,只能使我們得到了魚,失去了漁。今天,我們就來分享一個由USE DB堵塞故障引發的思考案例。
故障描述
今天一個朋友遇到資料庫遇到一個嚴重的故障,故障環境如下:
MYSQL 5.6.16
RR隔離級別
GITD關閉
表現如下:
use db不能進入資料庫
show table status不能查詢到表資訊
schema.processlist來看有大量的 Waiting for table metadata lock
情急之下他殺掉了一大堆執行緒後發現還是不能恢復,最後殺掉了一個沒有及時提交的事物才恢復正常。也僅僅留下了如下圖的一個截圖:
故障資訊提取
還是回到上圖,我們可以歸納一下語句型別如下:
1、CREATE TABLE A AS SELECT B
其STATE為 sending data
2、DROP TABLE A
其STATE為 Waiting for table metadata lock
3、SELECT * FROM A
其STATE為 Waiting for table metadata lock
4、 SHOW TABLE STATUS[like 'A']
其STATE為 Waiting for table metadata lock
資訊分析
要分析出這個案列其實不太容易因為他是MYSQL層MDL LOCK和RR模式innodb row lock的一個綜合案列,並且我們要對schema.processlist的STATE比較敏感才行。
建議先閱讀我的如下文章來學習MDL LOCK:
//www.jb51.net/article/131383.htm
本節關於MDL LOCK的驗證使用下面兩種方式:
方式一:筆者在MDL LOCK原始碼加鎖函式處加日誌輸出,如果要分析各種語句加MDL LOCK的型別還只能用這種方式,因為MDL LOCK加鎖往往一閃而過,performance_schema.metadata_locks 沒有辦法觀察到。
方式二:處於堵塞情況下使用5.7版本的performance_schema.metadata_locks觀察。
在P_S中開啟mdl監測方法如下:
一、關於CREATE TABLE A AS SELECT B 對B表sending data的分析
關於sending data這個狀態其實可以代表很多含義,從我現有的對的瞭解,這是MYSQL上層對SELECT型別語句的這類語句在INNODB層和MYSQL層進行資料互動的時候一個統稱,所以出現它的可能包含:
確實需要訪問資料量特別大,可能需要優化。
由於INNODB 層的獲取row lock需要等待,比如我們常見的SELECT FOR UPDATE。
同時我們還需要注意在RR模式下SELECT B這一部分加鎖方式和INSERT...SELECT是一致的參考不再贅述:
從他反應的情況因為他在最後殺掉了一個長期的未提交的事物所以他因為是情況2。並且整個CREATE TABLE A AS SELECT B語句由於B表上某些資料庫被上了鎖而不能獲取,導致整個語句處於sending data狀態下。
二、關於SHOW TABLE STATUS[like 'A'] Waiting for table metadata lock的分析
這是本案例中最重要的一環,SHOW TABLE STATUS[like 'A']居然被堵塞其STATE為Waiting for table metadata lock並且注意這裡是table因為MDL LOCK型別分為很多。我在MDL介紹的那篇文章中提到了desc 一個表的時候會上MDL_SHARED_HIGH_PRIO(SH),其實在SHOW TABLE STATUS的時候也會對本表上MDL_SHARED_HIGH_PRIO(SH)。
方式一
方式二
兩種方式都能觀察到MDL_SHARED_HIGH_PRIO(SH)的存在並且我模擬的是處於堵塞情況下的。
但是MDL_SHARED_HIGH_PRIO(SH) 是一個優先順序非常高的一個MDL LOCK型別表現如下:
相容性:
阻塞佇列優先順序:
其被堵塞的條件除了被MDL_EXCLUSIVE(X)堵塞沒有其他的可能。那麼這就是一個非常重要的突破口。
三、關於CREATE TABLE A AS SELECT B 對A表的加MDL LOCK的分析
這一點也是我以前不知道的,也是本案列中花時間最多的地方,前文已經分析過要讓SHOW TABLE STATUS[like 'A']這種只會上MDL_SHARED_HIGH_PRIO(SH) MDL LOCK的語句堵塞在MDL LOCK上只有一種可能那就是A表上了MDL_EXCLUSIVE(X)。
那麼我開始懷疑這個DDL語句在語句結束之前會對A表上MDL_EXCLUSIVE(X) ,然後進行實際測試不出所料確實是這樣的如下:
方式一
方式二
這裡比較遺憾在performance_schema.metadata_locks中並沒有顯示出MDL_EXCLUSIVE(X),而顯示為MDL_SHARED(S)是我們在我輸出的日誌中可以看到這裡做了升級操作將MDL_SHARED(S) 升級為了MDL_EXCLUSIVE(X)。並且由前面的相容性列表來看,只有MDL_EXCLUSIVE(X)會堵塞MDL_SHARED_HIGH_PRIO(SH)。所以我們應該能夠確認這裡確實做了升級操作,否則SHOW TABLE STATUS[like 'A'] 是不會被堵塞的。
四、關於SELECT * FROM A Waiting for table metadata lock的分析
也許大家認為SELECT不會上鎖,但是那是在innodb 層次,在MYSQL層會上MDL_SHARED_READ(SR) 如下:
方式一
方式二
可以看到確實有MDL_SHARED_READ(SR)的存在,當前處於堵塞狀態
其相容性如下:
顯然MDL_SHARED_READ(SR) 和MDL_SHARED_HIGH_PRIO(SH)是不相容的需要等待。
五、關於DROP TABLE A Waiting for table metadata lock的分析
這一點很好分析因為A表上了X鎖而DROP TABLE A必然上MDL_EXCLUSIVE(X)鎖它當然和MDL_EXCLUSIVE(X)不相容。如下:
方式一
方式二
其中EXCLUSIVE就是我們說的MDL_EXCLUSIVE(X)它確實存在當前處於堵塞
六、為何use db也會堵塞?
如果使用mysql客戶端不使用-A選項(或者 no-auto-rehash)在USE DB的時候至少要做如下事情:
1、 對db下每個表上MDL (SH) lock如下(呼叫MDL_context::acquire_lock 這裡給出堵塞時候的資訊)
方式一
方式二
可以看到USE DB確實也因為MDL_SHARED_HIGH_PRIO(SH) 發生了堵塞。
2、對每個表加入到table cache,並且開啟表(呼叫open_table_from_share())
那麼這種情況就和SHOW TABLE STATUS[like 'A']被堵塞的情況一模一樣了,也是由於MDL 鎖不相容造成的。
分析梳理
有了前面的分析那麼我們可以梳理這個故障發生的原因如下:
有一個在B表上長期未提交的DML
語句會在innodb層對B表某些資料加innodb row lock。
由步驟1引起了CREATE TABLE A AS SELECT B的堵塞
因為RR模式下SELECT B必然對B表上滿足的資料上鎖,因為步驟1已經加鎖所以觸發等待,STATE為sending data。
由步驟2引起了其他語句的堵塞
因為CRATE TABLE A AS SELECT B在A表建立完成之前會上MDL_EXCLUSIVE(X),這把鎖會堵塞其他全部的關於A表的語句,包括DESC/SHOW TABLE STATUS/USE DB(非-A) 這種只上MDL_SHARED_HIGH_PRIO(SH)MDL LOCK 的語句。STATE統一為Waiting for table metadata lock。
模擬測試
測試環境:
5.7.14
GITD關閉
RR隔離級別
使用指令碼:
步驟如下:
session1
最後我們看到的等待狀態如下:
這樣我們就完美的模擬出線上的狀態,如果我們殺掉session1中的事物,自然就全部解鎖了,讓我們再來看一下performance_schema.metadata_locks中的輸出:
我們可以看到如上的輸出,但是需要注意LOCK_TYPE: SHARED它不可能堵塞LOCK_TYPE: SHARED_HIGH_PRIO(可以參考附錄或者我以前寫的MDL LOCK分析的文章)如上文分析這裡實際上是做了升級操作升級為了MDL_EXCLUSIVE(X)。
總結
RC模式下雖然CREATE TABLE A SELECT B中B表不會上任何INNODB ROW LOCK但是如果B表非常大那麼A表也會處於MDL_EXCLUSIVE(X)保護下,因此也會觸發USE DB\SHOW TABLE STATUS等待的情況。
如果開啟GTID不能使用CREATE TABLE A SELECT B這樣的語句。
對於DML/DDL混用的系統一定要注意併發,就像本例中如果注意到高併發下的情況可以想辦法避免。
這個案列再次說明了長期不提交的事物可能引發悲劇,所以建議監控超過N秒沒結束的事務。
附錄
MDL LOCK TYPE
相容性矩陣
等待佇列優先順序矩陣