Mysql大表添加字段失敗問題及解決
背景
最近遇到一個問題,需要在user用戶表千萬級別數(shù)據(jù)中添加兩個字段,發(fā)現(xiàn)老是加不上去,一直卡死。
表數(shù)據(jù)量不僅大,而且是一個熱點表
,訪問頻率特別高,而且該表的訪問是在一個大事務(wù)中。加字段的時候一直在等待獲取MDL寫鎖
。
這個等待也影響了后續(xù)表訪問對MDL讀鎖
的獲取,導(dǎo)致后面的查詢也都被堵塞了。
更慘的是,客戶端有重試機制,查詢堵塞超過超時時間會再起一個session進(jìn)行請求,導(dǎo)致數(shù)據(jù)庫的線程池很快就爆滿了,直接掛掉。
MDL鎖
MDL鎖介紹
MDL鎖
屬于表級別的元數(shù)據(jù)鎖
。表級別鎖
分為數(shù)據(jù)鎖
和元數(shù)據(jù)鎖
,通常我們說的加鎖一般指的是加的數(shù)據(jù)鎖
。跟數(shù)據(jù)鎖
一樣,元數(shù)據(jù)鎖
也分讀鎖
和寫鎖
。
MDL
不需要顯示使用,在進(jìn)行表操作時會自動加上。當(dāng)對表數(shù)據(jù)進(jìn)行增刪改查
( insert、delete、select、update等)時,會自動加上MDL讀鎖
;當(dāng)要對表進(jìn)行加減字段
的結(jié)構(gòu)修改時,會自動加上MDL寫鎖
。
讀鎖不互斥
:意味著可以多個線程同時對一張表進(jìn)行增刪改查(CRUD)的操作。寫鎖獨占
:進(jìn)行結(jié)構(gòu)修改前,要先等待其他所有的MDL鎖
釋放了才能獲取到MDL寫鎖
。獲取到寫鎖后,在寫鎖釋放前,其他線程無法獲取到MDL讀鎖和寫鎖
。也就是說,修改一個表的結(jié)構(gòu)過程中,會阻塞其他線程對表的操作
MDL鎖
是MySQL自動隱式加鎖
,無需我們手動操作。在我們執(zhí)行DML語句
的時候,MySQL自動添加MDL讀鎖
。在我們執(zhí)行DDL語句
的時候,MySQL自動添加MDL寫鎖
。讀鎖與讀鎖之間不互斥,讀鎖與寫鎖、寫鎖與寫鎖之間互斥。注意:MDL鎖是表鎖,會對整張表加鎖
DML(Data Manipulation Language)
數(shù)據(jù)操縱語言:適用范圍:對表數(shù)據(jù)進(jìn)行操作,比如 insert、delete、select、update等。DDL(Data Definition Language)
數(shù)據(jù)定義語言:適用范圍:對表結(jié)構(gòu)進(jìn)行操作,比如create、drop、alter、rename、truncate等。
MDL鎖的必要性
MDL鎖
的存在,其實是為了保證 數(shù)據(jù)的一致性 。
想象一下,假如沒有 MDL鎖
,一個查詢在遍歷表數(shù)據(jù)的過程中,另外一個線程執(zhí)行了ALTER TABLE t DELETE COLUMN 'col_1'
把col_1這一列刪掉了,那查詢結(jié)果就亂了,結(jié)果中是否應(yīng)該有這一列數(shù)據(jù)?
所以為了保證并發(fā)操作下數(shù)據(jù)的一致性。
如果一個事務(wù)正在執(zhí)行中,另一個在這時修改了表結(jié)構(gòu),不但可能導(dǎo)致當(dāng)前事務(wù)出現(xiàn)不可重復(fù)讀的問題,還有可能連事務(wù)都無法提交
事故復(fù)現(xiàn)
介紹完MDL
鎖,我們再來復(fù)現(xiàn)下事故。我們通過下面的操作序列來模擬線上情況。
Session 1 | Session 2 | Session 3 |
---|---|---|
begin;select * from user limit 10; | ||
alter table user add ‘age’ int not null default ‘0’ comment ‘年齡’;(阻塞) | ||
select * from user limit 10; |
時刻1,事務(wù)1對表user進(jìn)行查詢,注意此時事務(wù)1并未提交,所以獲取的MDL讀鎖
也不會釋放。
時刻2另外一個線程想要添加字段, 由于 事務(wù)1正持著MDL讀鎖
,所以事務(wù)2會陷入阻塞
,等待事務(wù)1釋放讀鎖后
獲取MDL寫鎖
。
申請 MDL 鎖
的操作會形成一個隊列,隊列中寫鎖獲取優(yōu)先級高于讀鎖
。
所以事務(wù)2不僅阻塞了加字段的操作
,也會阻塞后續(xù)對該表的所有操作。比如后面的事務(wù)3查詢由于獲取不到MDL讀鎖都被阻塞了
。
這時,如果客戶端有重試機制,查詢超時后會重新進(jìn)行請求,容易把數(shù)據(jù)庫的連接池給擠爆了。
Mysql服務(wù)宕機的原因
為什么會出現(xiàn)這種情況呢?
原因是在執(zhí)行查詢語句的時候,MySQL自動加了MDL鎖(metadata lock,即元數(shù)據(jù)鎖)。
不行的話,我們可以再執(zhí)行一下show processlist
命令,查看有哪些正在執(zhí)行的進(jìn)程:
可以清楚的看到Session2和Session3的語句正在等待MDL鎖,Waiting for table metadata lock
。
解決方案
方案一
了解了原因,事情就比較好處理了,數(shù)據(jù)庫奔潰原因是由于加字段等待時間太長導(dǎo)致影響后續(xù)請求,但mysql
又無法在 alter table
語句里面設(shè)定等待時間。
所以當(dāng)時做法是繼續(xù)嘗試加字段語句,語句卡住30秒就手動cancel
掉。避免對后續(xù)請求的影響。重試了幾次發(fā)現(xiàn)一直沒能加上。最后是通過查看接口調(diào)用監(jiān)控,在請求頻率較低的時間點給加上了。
方案二
從MySQL5.6版本開始增加了Online DDL
,作用就是在執(zhí)行DDL
的時候,允許并發(fā)執(zhí)行DML
。簡單翻譯就是修改表結(jié)構(gòu)的時候,也能同時支持并發(fā)執(zhí)行增刪查改操作。
從MySQL8.0版本開始又優(yōu)化了Online DDL,支持快速添加列,可以實現(xiàn)給大表秒級加字段。具體用法就是在DDL
語句后面增加兩個參數(shù)ALGORITHM
和LOCK
。
比如下面這樣:
ALTER TABLE user ADD age int NOT NULL DEFAULT '0' COMMENT '年齡', ALGORITHM=Inplace, LOCK=NONE;
ALGORITHM可以指定使用哪種算法執(zhí)行DDL
,可選項有:
- Copy:拷貝方式,MySQL5.6 之前 DDL 的執(zhí)行方式,過程就是先創(chuàng)建新表,修改新表結(jié)構(gòu),把舊表數(shù)據(jù)復(fù)制到新表,刪除舊表,重命名新表。執(zhí)行過程非常耗時,產(chǎn)生大量的磁盤IO和占用CPU,還有使Buffer poll失效,而且需要鎖住舊表,性能較差,現(xiàn)在基本很少使用。
- Inplace:原地修改,MySQL5.6開始引入的,優(yōu)點是不會在Server層發(fā)生表數(shù)據(jù)拷貝,過程中允許并發(fā)執(zhí)行DML操作。過程就是先添加MDL寫鎖,執(zhí)行初始化操作,然后降級為MDL讀鎖,執(zhí)行DDL操作(比較耗時,允許并發(fā)執(zhí)行DML操作),升級為MDL寫鎖,完成DDL操作。
- Instant:快速修改,MySQL8.0開始引入的,可以實現(xiàn)快速給大表添加字段。
性能依次是,Instant > Inplace > Copy。
LOCK可以指定執(zhí)行過程中,是否加鎖,可選項有:
- NONE不加鎖,允許DML操作。
- SHARED加讀鎖,允許讀操作,禁止DML操作。
- DEFAULT默認(rèn)鎖模式,在滿足DDL操作前提下,默認(rèn)鎖模式會允許盡可能多的讀操作和DML操作。
- EXCLUSIVE加寫鎖,禁止讀操作和DML操作。
其他
這里我們重點關(guān)注INNODB_TRX, INNODB_LOCKS, 以及INNODB_LOCK_WAITS
三張表,表如其名,這三張表記錄了正在運行的事務(wù)(包括事務(wù)占用or釋放鎖的信息)狀態(tài)信息
select * FROM information_schema.INNODB_TRX; select * FROM information_schema.INNODB_LOCKS; select * FROM information_schema.INNODB_LOCK_WAITS; desc information_schema.INNODB_TRX; desc information_schema.INNODB_LOCKS; desc information_schema.INNODB_LOCK_WAITS show engine innodb status; show processlist;
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
MySQL9.1.0實現(xiàn)最基礎(chǔ)主從復(fù)制的步驟
本文主要介紹了使用Docker實現(xiàn)MySQL的主從復(fù)制,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02xtrabackup備份還原MySQL數(shù)據(jù)庫
這篇文章主要為大家詳細(xì)介紹了xtrabackup備份還原MySQL數(shù)據(jù)庫的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06mysql生成指定位數(shù)的隨機數(shù)及批量生成隨機數(shù)的方法
這篇文章主要介紹了mysql生成指定位數(shù)的隨機數(shù)及批量生成隨機數(shù)的方法,文中給大家介紹了常用mysql函數(shù),需要的朋友可以參考下2018-09-09MySQL數(shù)據(jù)庫Event定時執(zhí)行任務(wù)詳解
這篇文章主要介紹了MySQL數(shù)據(jù)庫Event定時執(zhí)行任務(wù)2017-12-12MYSQL數(shù)據(jù)庫導(dǎo)入數(shù)據(jù)時出現(xiàn)亂碼的解決辦法
我是用的最后一種方法,前面三種解決MYSQL導(dǎo)入數(shù)據(jù)亂碼的方法沒試過,東莞SEO推薦大家直接使用第四種方法處理MYSQL導(dǎo)入中文數(shù)據(jù)時的亂碼問題。2011-01-01MYSQL設(shè)置字段自動獲取當(dāng)前時間的sql語句
整理數(shù)據(jù)庫數(shù)據(jù),看到好多表都有加create_time和 update_time字段,來記錄數(shù)據(jù)插入的時間和更新時間,但是時間插入是通過代碼來維護(hù)的,這篇文章主要介紹了MYSQL設(shè)置字段自動獲取當(dāng)前時間,需要的朋友可以參考下2023-07-07