【科普】MySQL中DDL操作背后的並發原理


一. 簡介

DQL:指數據庫中的查詢(select)操作。 
DML:指數據庫中的插入(insert)、更新(update)、刪除(delete)等行數據變更操作。 
DDL:指數據庫中加列(add column)、修改列(change column)、創建索引(create index)、刪除索引(drop index)、刪除表(drop table)、清理表(truncate table)等表結構定義操作。

經常有同學會碰到索引加不上,或者drop table卡住等DDL執行問題,很想和他們解釋背后原理,但是三言兩語解釋不通,所以寫了這篇文檔。

本篇主要介紹下MySQL中DDL操作背后的鎖原理,希望通過閱讀這篇文檔,你可以掌握如下兩點:

  1. 了解MySQL中的DDL鎖機制,理解不要在業務期間進行DDL操作這句話的背后原理。

  2. 知道如何去查看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個步驟:

  1. PREPARE:會申請MDL X鎖,然后更新數據字典並分配row_log開始記錄表上的DML增量數據,這個過程如果沒有被MDL鎖阻塞,那么是非常快的。

  2. DDL:將所持有的MDL X鎖降級為MDL S鎖

    • 一方面進行數據拷貝,比如建二級索引或者重建原表。

    • 另一方面記錄這期間產生的DML日志,寫到row_log中后進行數據重放。

  3. 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狀態

  1. 通過dbeaver中的會話管理窗口可以很方便的看到MySQL中的SQL運行狀態。

  1. 我們先后對emp表執行了慢查詢與加列操作,通過會話窗口可以看到加列的DDL操作處於Waiting for table metadata lock狀態,這個狀態其實就是處於MDL鎖等待,DDL操作因為沒有申請到MDL X鎖(因為慢查詢占了MDL S鎖並且一直沒釋放所致),所以一直處於等待狀態,並不在真正運行。

  1. 這個MDL 鎖等待狀態不會一直持續下去,MySQL中通過lock_wait_timeout參數控制這個超時的時間,bin包中默認配置為120秒,超過120秒還沒申請到MDL鎖就會拋出錯誤,讓用戶感知到。

  1. 如果想要快速解決DDL無法得到MDL寫鎖的問題,就可以手動KILL目前占用MDL鎖的會話,讓DDL獲取MDL寫鎖,比如這里就可以通過KILL 會話ID的命令或者右擊慢查詢會話選擇結束會話的方式進行KILL。

4.2. 實驗二

模擬過程:模擬執行一個慢查詢(通過sleep函數),然后進行表上的加列操作,再對這張表進行SELECT查詢

  1. 當DDL加列操作處於Waiting for table metadata lock狀態時,我們接着去查詢emp表,可以看到這個查詢也會處於等待MDL鎖,雖然這個查詢只是申請MDL讀鎖,並且DDL操作並沒有申請到MDL寫鎖,但還是會處於等待狀態,其原理就如之前所說的,MDL鎖申請遵循一個隊列機制,即使DDL操作並未獲取MDL寫鎖,后續表上的其他操作也會認為DML寫鎖已經被獲取,要等待這個DDL操作結束才可以獲取到MDL鎖。

  1. 當DDL加列操作因為長時間未獲取MDL X鎖而超時后,后面的查詢也就可以獲取到MDL S鎖,然后可以進行查詢。

4.3. 實驗三

模擬過程:模擬Online DDL的過程,首先對一張大表進行DDL加列操作,然后對該表進行SELECT慢查詢(通過sleep函數延長SQL執行時間)

  1. DDL快速的申請到了MDL寫鎖后降級為MDL讀鎖,然后開始重建表過程,這時候可以看到DDL狀態為altering table即正在進行DDL操作的一種正常SQL狀態,SELECT查詢也處於正常狀態,並沒有等待MDL鎖,因為這時候大家都是持有的MDL讀鎖,並不存在沖突。

  1. 過了一會當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鎖爭用問題,對應表上的業務均會癱瘓掉,連接數暴漲,最后連接池連接達到上限,整個系統也會癱瘓掉。

六. 寫在最后

【科普】系列文章主要內容為總結整理數據庫方面的常用知識及背后原理,面向所有開發與運維人員,希望以此來增加數據庫的知識網絡,更好的在項目上使用與管理好數據庫。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM