給表新增欄位時,發現鎖表了,檢視程式,提示Waiting for table metadata lock
,等待鎖釋放;然而蛋疼的是幾分鐘過去了,依然沒有任何的進展,特此記錄下這個問題的定位過程以及MDL的相關背景知識
看到上面的表現,基本問題就來了
- Metadata Lock 是什麼鬼
- 是什麼原因導致一直等待
I. 問題定位
首先需要確認什麼地方加鎖,從mysql出發,應該怎麼定位?
1. 定位過程
對於mysql而言,一般來講上鎖和事物時伴生關係,所以我們的直觀出發點就是查詢db當前正在執行的事物
-- 查詢當前正在執行的事物的sql
SELECT * FROM information_schema.INNODB_TRX;
複製程式碼
輸出結果如下,首先拿到事物對應的程式id
拿到id之後,則可以分析對應的程式資訊
-- 查詢程式資訊
show processlist
-- 查詢所有的程式資訊
show full processlist
複製程式碼
然後定位到具體的程式
然後登陸到目標機器,檢視埠號對應的程式,通過lsof
命令檢視
lsof -i tcp:52951
複製程式碼
從圖中可以看出,是一個python程式的mysql連線開啟的事物,程式id為5436
接著檢視程式對應的資訊
ps aux | grep 5436
複製程式碼
這個指令碼正是測試aiomysql的python指令碼,內容比較簡單
import asyncio
import aiomysql
loop = asyncio.get_event_loop()
@asyncio.coroutine
def test_example():
conn = yield from aiomysql.connect(host='127.0.0.1', port=3306,
user='root', password='', db='test',
loop=loop, autocommit=False)
cur = yield from conn.cursor()
yield from cur.execute("SELECT * from test_table")
print(cur.description)
r = yield from cur.fetchall()
print(r)
yield from cur.close()
conn.close()
loop.run_until_complete(test_example())
複製程式碼
2. 原因分析
對python不太熟,直接藉助google查一下,發現有同樣的問題
這個問題丟擲,在通過with開啟連線獲取遊標後,執行mysql,但是沒有commit之前,會鎖表,這個期間修改表都會出現等待
下面近給出瞭解答,並沒有看到更多的深層次的說明,先記錄下,解決辦法就是在建立連線池的時候,選擇自動提交方式,然後就不會有這個問題了
pool = await aiomysql.create_pool(
host="localhost",
user="test",
password="test",
db="test",
autocommit=True,
cursorclass=DictCursor,
loop=loop)
複製程式碼
II. Metadata Lock說明
找到一篇文章說MDL的,推薦詳細閱讀 MySQL表結構變更你不可不知的Metadata Lock詳解
1. MDL 說明
抓一下核心的要點,簡單說一下看完這篇文章之後的樸素理解
MetaData Lock 簡稱為MDL,簡單來說就是表的後設資料鎖;當修改表結構的時候,就需要持有這個鎖
a. 作用
MDL的主要作用只有一點,保護一個正在執行的事物表結構不被修改
有一個原則,MDL是事物級別的,只有事物結束之後才會釋放,而這裡面說的事物分為兩類
- 顯示事物:
- 關閉autocommit
- 以begin或start transaction開始的操作
- AC-NL-RO(auto-commit non-locking read-only):
- auto commit 開啟之下的select操作
b. 例項說明
直接看上面的說明,不太直觀,一個經典的case如下
session1 開啟了一個事物,執行查詢操作;但是現在session2 要刪除表,如果執行成功,那麼session1的第二次查詢就跪了,這樣就違背了事物的原則,所有在5.5版本引入了MDL,來保證在事物執行期間,表結構不被修改
2. 出現MDL等待原因及解決方法
當我們出現修改表結構,就需要獲取MDL的排他鎖,因此只有這個表沒有事物在執行時,才能獲取成功;當持有獨佔鎖之後,這個表的其他操作將被阻塞(即不能插入資料,修改資料,也不能開啟事物操作)
因此在執行DDL時,一直出現等待MDL的時候,常見的原因有下面三個
a. 長事物,阻塞DDL,從而阻塞所有同表的後續操作
通過 show processlist看到表上有正在進行的操作(包括讀),此時修改表時也會等待獲取MDL,這種時候解決辦法要麼就是等待執行完畢,要麼就是直接kill掉程式
b. 未提交事物,阻塞DDL
通過 show processlist沒有找到表上的操作,但是通過information_schema.innodb_trx
發現有未提交的事物,
c. 異常的狀況
通過 show processlist 和事物查詢都沒有的情況下,可能的場景是一個顯示的事物中,對錶的操作出現了異常,雖然事物失敗,但是持有的鎖還沒有釋放,也會導致這個原因
可以在performance_schema.events_statements_current
表中查詢失敗的語句
3. MDL分類與sql例項
前面兩小節,分別說明什麼是MDL(樸素理解為表的後設資料鎖),以及當修改表時出現長時間的等待MDL的原因分析;正常看完之後,應該會有下面的疑惑
- MDL有哪些型別
- 哪些sql會持有MDL
對於MDL的型別,從網上截一張圖
接下來需要分析下不同鎖模式對應的sql
屬性 | 含義 | 事例 |
---|---|---|
MDL_INTENTION_EXCLUSIVE(IX) |
意向排他鎖用於global和commit的加鎖。 | truncate table t1; insert into t1 values(3,’abcde’); 會加如下鎖 (GLOBAL,MDL_STATEMENT,MDL_INTENTION_EXCLUSIVE)(SCHEMA,MDL_TRANSACTION,MDL_INTENTION_EXCLUSIVE) |
MDL_SHARED(S) |
只訪問後設資料 比如表結構,不訪問資料。 | set golbal_read_only =on 加鎖 (GLOBAL,MDL_EXPLICIT,MDL_SHARED) |
MDL_SHARED_HIGH_PRIO(SH) |
用於訪問information_scheam 表,不涉及資料。 |
select * from information_schema.tables;show create table xx; desc xxx; 會加如下鎖: (TABLE,MDL_TRANSACTION,MDL_SHARED_HIGH_PRIO) |
MDL_SHARED_READ(SR) |
訪問表結構並且讀表資料 | select * from t1; lock table t1 read; 會加如下鎖: (TABLE,MDL_TRANSACTION,MDL_SHARE_READ) |
MDL_SHARED_WRITE(SW) |
訪問表結構並且寫表資料 | insert/update/delete/select .. for update 會加如下鎖:(TABLE,MDL_TRANSACTION,MDL_SHARE_WRITE) |
MDL_SHARED_UPGRADABLE(SU) |
是mysql5.6引入的新的metadata lock,可以說是為了online ddl 才引入的。特點是允許DML,防止DDL; | alter table/create index/drop index 會加該鎖; 加入下鎖 (TABLE,MDL_TRANSACTION,MDL_SHARED_UPGRADABLE) |
MDL_SHARED_NO_WRITE(SNW) |
可升級鎖,訪問表結構並且讀寫表資料,並且禁止其它事務寫。 | alter table t1 modify c bigint; (非onlineddl) (TABLE,MDL_TRANSACTION,MDL_SHARED_NO_WRITE) |
MDL_SHARED_NO_READ_WRITE(SNRW) |
可升級鎖,訪問表結構並且讀寫表資料,並且禁止其它事務讀寫。 | lock table t1 write; 加鎖 (TABLE,MDL_TRANSACTION,MDL_SHARED_NO_READ_WRITE |
MDL_EXCLUSIVE(X) |
防止其他執行緒讀寫後設資料 | CREATE/DROP/RENAME TABLE ,其他online DDL在rename階段也持有X鎖(TABLE,MDL_TRANSACTION,MDL_EXCLUSIVE) |
4, 小結
上面的內容,可能資訊量比較大,特別是MDL的鎖分類情況,很難抓住重點,針對我們日常接觸中,簡單給出小結
- MDL是為了保證事物執行過程中,表結構不被修改引入的;因此修改表結構的前提是這個表上沒有事物(沒有正在執行,失敗,或者未提交的事物)
- DDL執行,一般來講是需要獲取排他的MDL
- DML都會開啟事物,因此會獲取
MDL_SW
鎖 - DQL語句會獲取
MDL_SR
鎖
幾個簡稱的說明
- MDL: metadata lock,可以簡單理解為表的後設資料鎖
- DDL: 資料定義語言,可以簡單理解為表的操作,如建立,修改,刪除表、檢視等,新增索引、欄位等操作
- DML: 資料操作語言,也就是我們常規理解的
insert, update, delete
語句 - DQL: 資料查詢語言,常見的
select
語句
幾個常見疑問解答
a. 為什麼同一張表的多個DDL不能並行執行
- MDL讀鎖是互相相容的,可以有多個增刪查改
- MDL寫鎖是互斥的,只能有一個表的DDL
b. 為什麼有時候DDL會卡住
- MDL讀寫鎖之間是互斥的,所以如果DDL卡住,就證明有事務在執行,不能申請MDL寫鎖
c. 常見卡住的場景
- 非常頻繁的業務高峰期
- 有慢查詢把持著MDL讀鎖
- 有事物一直未提交
d. 為什麼需要MDL鎖
- 當事務本身執行的時候理論上是不能容忍表結構在中途發生改變的
5. 更多參考
相關博文或者問答
- Why aiomysql locks the table even when using context manager?
- MySQL表結構變更你不可不知的Metadata Lock詳解
- 理解MySQL的MDL後設資料鎖
II. 其他
1. 一灰灰Blog: https://liuyueyi.github.io/hexblog
一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 宣告
盡信書則不如,已上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
3. 掃描關注
一灰灰blog
知識星球