MySQL 5.5+ 中 ALTER TABLE 卡住大概率因 MDL 锁阻塞,DDL 需 EXCLUSIVE 锁而被未提交 DML 或长事务持有的 SHARED 锁阻塞,需通过 performance_schema.metadata_locks 定位持锁源头。

MySQL 5.5+ 中 ALTER TABLE 被卡住,大概率是 MDL 锁在作祟
MDL(Metadata Lock)不是你显式加的锁,而是 MySQL 自动加的——只要语句访问了表结构,就会申请对应粒度的 MDL 锁。DDL 操作(如 ALTER TABLE)需要 EXCLUSIVE 级别 MDL 锁,而任何未提交的 DML(哪怕只是 SELECT 后没 COMMIT)都可能持有更弱但兼容性更低的锁(比如 SHARED_READ),导致 DDL 无限等待。
常见错误现象:ALTER TABLE t1 ADD COLUMN c1 INT 执行不动,SHOW PROCESSLIST 里状态长期卡在 Waiting for table metadata lock;同时查 performance_schema.metadata_locks(需开启)能看到阻塞链。
- 必须确认是否有长事务:执行
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60 - 注意隐式事务:
AUTOCOMMIT=0下,一个SELECT后没COMMIT,也会持锁 -
FLUSH TABLES WITH READ LOCK或备份工具(如mysqldump --single-transaction以外模式)也会强占SHARED_NO_WRITE锁,挡住 DDL
查谁在 hold MDL 锁?别只看 PROCESSLIST,要挖 performance_schema
SHOW PROCESSLIST 只显示当前活跃连接,但真正 hold 锁的可能是已断开但事务未清理的线程,或后台线程(如复制 SQL 线程)。必须依赖 performance_schema.metadata_locks 表定位源头。
使用前提:MySQL ≥ 5.7,且 performance_schema 开启,metadata_locks 表采集开关打开(setup_instruments 中 wait/lock/metadata/sql/mdl 设为 YES)。
- 查当前所有 MDL 锁:
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_DURATION, LOCK_STATUS, OWNER_THREAD_ID FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA NOT IN ('performance_schema', 'mysql', 'information_schema') - 结合
threads表找线程名:SELECT NAME, PROCESSLIST_INFO FROM performance_schema.threads WHERE THREAD_ID = ? - 特别注意
LOCK_DURATION = 'TRANSACTION'的行——这类锁会持续到事务结束,哪怕线程已空闲
ALTER TABLE ... ALGORITHM=INPLACE 并不能绕过 MDL 排队
很多人以为加了 ALGORITHM=INPLACE 就能“不锁表”,其实它只影响数据变更阶段是否重建表,**不影响 MDL 锁的申请时机和级别**。无论用什么算法,ALTER TABLE 开始前仍需获取 EXCLUSIVE MDL 锁,照样会被前面的 DML 阻塞。
-
ALGORITHM=COPY和INPLACE在 MDL 行为上完全一致:都需要先等所有已有 MDL 兼容锁释放 -
LOCK=NONE是控制 DML 并发能力的参数,不是锁类型开关;即使设了LOCK=NONE,DDL 本身仍需排他 MDL - 真正降低影响的方式是缩短 DDL 等待窗口:提前 kill 掉长事务、避免在业务高峰执行、用
pt-online-schema-change这类工具分阶段操作
线上紧急解法:别直接 KILL,先看线程在干啥
发现某个线程被 ALTER TABLE 卡住,第一反应不是 KILL,而是确认它是否正在做关键业务操作。盲目 KILL 可能导致事务回滚耗时更长,甚至引发主从延迟突增。
- 查该线程最后执行语句:
SELECT PROCESSLIST_INFO FROM performance_schema.threads WHERE THREAD_ID = ? - 查其事务状态:
SELECT trx_id, trx_state, trx_started, trx_query FROM information_schema.INNODB_TRX WHERE trx_mysql_thread_id = ? - 如果
trx_state = 'RUNNING'且trx_query是重要更新,优先协调业务侧主动COMMIT或ROLLBACK - 万不得已
KILL时,用KILL QUERY(终止当前语句)比KILL(终止整个连接)更安全,尤其对AUTOCOMMIT=0场景
MDL 的复杂在于它藏得深、生命周期和事务绑定、又无法被普通 SELECT 显式感知。排查时最容易忽略的是“已经没在跑 SQL,但事务还开着”这种静默持锁状态。










