根本原因是MDL锁互斥:ALTER TABLE需获取元数据锁(MDL)写锁,而未提交事务或慢查询持有MDL读锁,导致后续所有DML和SELECT均因无法获取对应MDL锁而阻塞。

根本原因不是“DDL本身锁表”,而是它必须等待并持有元数据锁(MDL),而这个锁会阻塞所有后续的DML请求——哪怕你用的是ALGORITHM=INPLACE或LOCK=NONE。
为什么ALTER TABLE一执行,SELECT/INSERT就卡住
MySQL 5.5+ 引入 MDL(Metadata Lock)机制,任何对表结构的访问(包括SELECT)都需先获取 MDL 读锁,而 DDL(如ALTER TABLE)必须获得 MDL 写锁。写锁与读锁互斥,所以只要有一个长事务还没释放该表的 MDL 读锁(比如一个没提交的SELECT ... FOR UPDATE或慢查询),ALTER TABLE就会卡在“waiting for table metadata lock”状态;此时所有新来的 DML(甚至普通SELECT)也因拿不到读锁而排队等待。
常见现象:
- 执行
SHOW PROCESSLIST能看到一堆Waiting for table metadata lock状态的线程 -
information_schema.INNODB_TRX里有运行超 60 秒的事务,且TRX_QUERY含对该表的 DML - 从库延迟突增,
SHOW SLAVE STATUS中Seconds_Behind_Master不变但Exec_Master_Log_Pos卡住
ALGORITHM=INPLACE和LOCK=NONE并不能绕过 MDL 等待
这两个参数只影响“是否重建表”和“是否允许并发 DML”,但不改变“获取 MDL 写锁”的前提。也就是说:
-
ALGORITHM=INPLACE≠ 不需要 MDL 写锁,只是 DML 可以在 DDL 执行中并发进行(前提是 MDL 写锁已拿到) -
LOCK=NONE≠ 不阻塞 DML,而是表示“允许 DML 并发”,但它依然要等前面所有 MDL 读锁释放完才能开始 - 哪怕只是
ALTER TABLE t MODIFY COLUMN c VARCHAR(255),在 MySQL 8.0.12 之前仍可能触发全表 COPY,进一步延长 X 锁持有时间,加剧阻塞
如何快速定位并解除阻塞源头
别急着 kill DDL,先查谁在占着 MDL 读锁:
- 查活跃长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(TRX_STARTED) > 60 - 查哪些线程正在等锁:
SELECT * FROM performance_schema.threads t JOIN performance_schema.events_statements_current e USING(thread_id) WHERE e.SQL_TEXT LIKE '%your_table_name%' - 查锁等待链:
SELECT * FROM performance_schema.data_lock_waits(MySQL 8.0.1+)或SELECT * FROM information_schema.INNODB_LOCK_WAITS(5.7) - 确认后,用
KILL [thread_id]干掉持有锁的慢查询或未提交事务(注意:KILL 的是trx_mysql_thread_id,不是TRX_ID)
线上执行 DDL 最容易被忽略的三个点
很多人以为加了ALGORITHM=INPLACE就安全了,其实真正危险的环节藏在这儿:
- DDL 开始前没人检查
INNODB_TRX,结果一执行就卡住,而应用侧连接池已耗尽 - 误以为
LOCK=NONE等于“零感知”,但没意识到它仍需等老事务释放 MDL——尤其在主从架构下,从库可能因复制延迟导致“看起来没锁,实则卡在等主库事务结束” - 用
pt-online-schema-change时删了原表触发器却忘了清理,导致后续 DML 持续写影子表失败,反而引入新阻塞点


















