一. 簡介
DQL:指數據庫中的查詢(select)操作。
DML:指數據庫中的插入(insert)、更新(update)、刪除(delete)等行數據變更
操作。
DDL:指數據庫中加列(add column)、修改列(change column)、創建索引(create index)、刪除索引(drop index)、刪除表(drop table)、清理表(truncate table)等表結構定義
操作。
經常有同學會碰到索引加不上,或者drop table卡住等DDL執行問題,很想和他們解釋背后原理,但是三言兩語解釋不通,所以寫了這篇文檔。
本篇主要介紹下MySQL中DDL操作背后的鎖原理,希望通過閱讀這篇文檔,你可以掌握如下兩點:
-
了解MySQL中的DDL鎖機制,理解
不要在業務期間進行DDL操作
這句話的背后原理。 -
知道如何去查看DDL操作的實時狀態及如何去解決MDL鎖爭用的問題。
二. MDL鎖介紹
從MySQL5.5版本開始引入了MDL鎖
,全稱為metadata lock,即元數據鎖
。MDL鎖的主要作用是維護表元數據的數據一致性,當表上有活動事務(注意MDL鎖伴隨事務提交而釋放,而不是SQL結束而釋放)
的時候,不可以對元數據(即表結構)
進行任何修改操作。
PS:MDL共享鎖 = MDL讀鎖 = MDL S鎖
, MDL排他鎖 = MDL寫鎖 = MDL X鎖
,下文中叫法不同,但是含義一致。
粗略畫了下DDL與DML(這里其實也包括DQL)的鎖申請過程,大家可以對照着這幅圖來理解MDL鎖,簡單來說,DDL操作
會申請對應表上的的MDL X鎖
,這把鎖是排他鎖
,一旦申請成功,該表上的其他所有操作都無法進行(包括DDL、DML、DQL),因為無法再申請到該表上的MDL鎖,直到DDL操作申請的MDL X鎖釋放為止。
而DML或DQL操作
都只會申請MDL S鎖
,而S鎖為共享鎖
,可以支持並發訪問,因此大量相同表上的增刪改查操作是可以並發執行的。
這里還需要了解的一點是,MDL鎖申請遵循一個隊列機制
,即先到先得,因此如果一個DDL操作一直無法得到MDL X鎖,那么后續所有該表上的SQL都會等待這個DDL操作拿到MDL X鎖並且釋放為止,這也是為啥我們經常聽到不要在業務期間進行DDL操作
的原因之一,DDL操作很容易因為某個慢SQL導致后續所有的SQL都被卡住(等待MDL鎖)。
三. Online DDL
MySQL 5.6 Online DDL推出以前,執行DDL主要有兩種方式copy方式
和inplace方式(只支持添加、刪除索引)
,DDL執行期間會全程鎖表,無法同時進行DML,實用性很低。
-
copy:建一張新表結構的臨時表,鎖原表禁止DML,然后拷貝數據到臨時表,升級字典鎖,禁止原表讀寫,進行rename操作,完成DDL。
-
inplace(fast index creation):新建索引的數據字典,鎖原表禁止DML,然后在原表基礎上讀取數據添加索引,等待表上所有只讀事務提交,完成DDL。
MySQL 5.6 版本發布了Online DDL
功能,顧名思義,就是在DDL執行期間,也可以同時進行表上的DML操作,並不會全程鎖表,實用性加強了很多。
目前我們將MySQL DDL操作划分為三種執行方式:
-
copy
:創建新表結構的臨時表,鎖原表禁止DML,將數據copy到臨時表,完成后刪除原表,重命名新表,需要拷貝原始表,執行過程中源表不允許寫但可讀
。 -
inplace
:在進行DDL操作時,MDL寫鎖會降級為MDL讀鎖
,這樣就可以支持並發DML
,然后通過row_log記錄原表上的DML增量操作,最后通過回放增量數據保證數據一致性。-
rebuild table:部分DDL操作類型在inplace模式下,需要進行
重建表(原表基礎上進行更新)
,往往表越大越費時
。 -
no rebuild table:部分DDL操作類型在inplace模式下,不需要重建表,往往只需要修改元數據,因此
速度比較快
。
-
-
instant
:從MySQL 8.0.12
才開始引入,加列操作可以不需要重建表,只需要修改元數據,可以實現秒加列。
下面列舉一些常見的DDL操作(紅色標記代表不支持online DDL,只支持copy):
DDL操作類型 | DDL執行速度 | 是否支持inplace模式(Online DDL) | rebuild table(是否需要重建表) | 是否支持並發讀寫 | 只需要修改元數據 |
增加列 | 表越大速度越慢 | Yes | Yes | Yes | No |
刪除列 | 表越大速度越慢 | Yes | Yes | Yes | No |
修改列類型 |
表越大速度越慢 | No |
Yes | No |
No |
擴展varchar列長度(255字節以下或255字節以上區間內調整) | 秒級完成 | Yes | No | Yes | Yes |
擴展varchar列長度(從255字節內到255字節外) |
表越大速度越慢 | No |
Yes | No |
No |
創建二級索引 | 表越大速度越慢 | Yes | No | Yes | No |
刪除二級索引 | 秒級完成 | Yes | No | Yes | Yes |
表字符集轉換 |
表越大速度越慢 | No |
yes | No |
No |
drop or truncate table(比較特殊,申請到MDL寫鎖就可以快速完成) | 秒級完成 | NULL | NULL | NULL | NULL |
關於Online DDL的實現原理,大概如下:
這幅圖比較繁瑣,但是很詳細,我們只需要關注紅框的步驟,整個DDL主要划分為3個步驟:
-
PREPARE:會
申請MDL X鎖
,然后更新數據字典並分配row_log開始記錄表上的DML增量數據,這個過程如果沒有被MDL鎖阻塞,那么是非常快的。 -
DDL:將所持有的
MDL X鎖降級為MDL S鎖
。-
一方面進行數據拷貝,比如建二級索引或者重建原表。
-
另一方面記錄這期間產生的DML日志,寫到row_log中后進行數據重放。
-
-
COMMIT:等到重放至最后一個block時,從
MDL S鎖
升級到MDL X鎖
,回放最后一個block,最后更新數據字典,完成DDL,釋放MDL鎖。
整個Online的實現主要依賴於row_log記錄DDL期間的DML增量日志,這樣就可以不用一直占用MDL X鎖,而只需要占用一瞬間,期間主要持有MDL S鎖即可。
值得注意的是,Online DDL前后需要申請兩次MDL X鎖,雖然持有時間非常短,如果存在慢SQL的話,還是會引起大量MDL鎖等待的問題。
四. MDL鎖實驗
為了方便大家更好的理解MDL鎖,我在dbeaver中進行了如下幾個MDL鎖模擬實驗。
4.1. 實驗一
模擬過程:模擬執行一個慢查詢(通過sleep函數),然后進行表上的加列操作,查看DDL狀態
。
-
通過dbeaver中的會話管理窗口可以很方便的看到MySQL中的SQL運行狀態。
-
我們先后對
emp表
執行了慢查詢與加列操作,通過會話窗口可以看到加列的DDL操作處於Waiting for table metadata lock
狀態,這個狀態其實就是處於MDL鎖等待,DDL操作因為沒有申請到MDL X鎖(因為慢查詢占了MDL S鎖並且一直沒釋放所致),所以一直處於等待狀態,並不在真正運行。
-
這個MDL 鎖等待狀態不會一直持續下去,MySQL中通過
lock_wait_timeout
參數控制這個超時的時間,bin包中默認配置為120秒,超過120秒還沒申請到MDL鎖就會拋出錯誤,讓用戶感知到。
-
如果想要快速解決DDL無法得到MDL寫鎖的問題,就可以手動KILL目前占用MDL鎖的會話,讓DDL獲取MDL寫鎖,比如這里就可以通過
KILL 會話ID
的命令或者右擊慢查詢會話選擇結束會話
的方式進行KILL。
4.2. 實驗二
模擬過程:模擬執行一個慢查詢(通過sleep函數),然后進行表上的加列操作,再對這張表進行SELECT查詢
。
-
當DDL加列操作處於
Waiting for table metadata lock
狀態時,我們接着去查詢emp表,可以看到這個查詢也會處於等待MDL鎖,雖然這個查詢只是申請MDL讀鎖,並且DDL操作並沒有申請到MDL寫鎖,但還是會處於等待狀態,其原理就如之前所說的,MDL鎖申請遵循一個隊列機制
,即使DDL操作並未獲取MDL寫鎖,后續表上的其他操作也會認為DML寫鎖已經被獲取,要等待這個DDL操作結束才可以獲取到MDL鎖。
-
當DDL加列操作因為長時間未獲取MDL X鎖而超時后,后面的查詢也就可以獲取到MDL S鎖,然后可以進行查詢。
4.3. 實驗三
模擬過程:模擬Online DDL的過程,首先對一張大表進行DDL加列操作,然后對該表進行SELECT慢查詢(通過sleep函數延長SQL執行時間)
。
-
DDL快速的申請到了MDL寫鎖后降級為MDL讀鎖,然后開始重建表過程,這時候可以看到DDL狀態為
altering table
即正在進行DDL操作的一種正常SQL狀態,SELECT查詢也處於正常狀態,並沒有等待MDL鎖,因為這時候大家都是持有的MDL讀鎖,並不存在沖突。
-
過了一會當DDL操作完成后,想要從MDL讀鎖升級到MDL寫鎖進行COMMIT時,發現無法申請到,因為這時候SELECT查詢還占用着MDL讀鎖,所以處於
Waiting for table metadata lock
狀態。這時候如果沒有外界干預,要么等待120秒后DDL超時回滾,要么等待SELECT在超時期間內結束,釋放MDL鎖,這樣就可以成功完成DDL操作。
五. 總結
MySQL中DDL操作通過MDL這把鎖來保證了表結構與表數據的一致性,而Online DDL特性則使得DDL使用更加方便與輕量。
大家在進行DDL操作后,一定要確認DDL是否處於真正的ALTER狀態,還是等待MDL鎖的狀態,如果是等待MDL鎖,則需要找到對應占用MDL鎖的會話(通常都是一個或多個對應表上執行很慢的SELECT查詢
),這時候可以判斷是否進行KILL來讓DDL正常執行。
最后提醒大家,業務高峰期千萬不要進行核心業務表的DDL操作
,保不齊MySQL里就存在該表上的一條運行比較慢的SQL或掛起的事務,那么就非常容易引起連鎖的MDL鎖爭用問題,對應表上的業務均會癱瘓掉,連接數暴漲,最后連接池連接達到上限,整個系統也會癱瘓掉。
六. 寫在最后
【科普】系列文章主要內容為總結整理數據庫方面的常用知識及背后原理
,面向所有開發與運維人員,希望以此來增加數據庫的知識網絡,更好的在項目上使用與管理好數據庫。