真正卡住ddl的是持有s mdl锁的长事务,需查innodb_trx中trx_state='running'且trx_started过早的记录,再用sys.schema_table_lock_waits定位blocking_pid并kill释放锁。

Waiting for table metadata lock 是谁在卡DDL
看到这个状态,说明已经有 ALTER TABLE 被挂起了,它不是源头,而是受害者。真正的问题是某个长事务或未提交的查询正拿着 S MDL 锁不放。
绝大多数阻塞根源不在 DDL 本身,而在一个没提交的 SELECT、UPDATE 或隐式事务——比如应用执行完 BEGIN 就断开连接,或者 Python 连接池复用连接但没显式 conn.close()。
- 查活跃事务:运行
SELECT * FROM information_schema.innodb_trx WHERE trx_state = 'RUNNING',重点关注trx_started时间,别只看trx_mysql_thread_id - 如果
TRX_QUERY为空,大概率是 autocommit=0 下只执行了语句没COMMIT,属于隐式事务 -
SHOW PROCESSLIST只能看到“卡住”,看不到“为什么卡”;Time列值大 ≠ 真正在跑,得结合State和Info综合判断
用 sys.schema_table_lock_waits 快速定位阻塞链
MySQL 8.0 默认启用 sys 库,schema_table_lock_waits 是现成的阻塞关系图谱,比手拼表快得多。
直接运行 SELECT * FROM sys.schema_table_lock_waits\G,重点看三列:
-
BLOCKING_TRX_ID:持有锁的事务 ID -
BLOCKING_PID:对应线程 ID,可直接KILL -
BLOCKING_SQL:如果为空,说明阻塞者是隐式事务;如果不为空,比如是SELECT ... FOR UPDATE或UPDATE,就更有判断依据
注意:该视图依赖 performance_schema 正常采集,若查不到结果,先确认是否有 DDL 正在执行(如 ALTER TABLE),否则可能根本没触发 MDL 等待。
KILL 前必须确认事务上下文
盲目 KILL 会导致数据不一致或应用报错,尤其不能只看 TRX_ROWS_MODIFIED = 0 就杀——它可能刚执行完 SQL 正卡在应用层没提交,也可能正在做关键业务逻辑。
- 先查锁影响范围:
SELECT trx_id, trx_state, trx_isolation_level, trx_rows_modified FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = ? -
KILL QUERY <code>pid对 MDL 无效,它只中断当前语句,事务和锁还在;必须用KILL <code>pid终止整个连接才能释放 S MDL - 如果阻塞者是报表导出或批量更新这类合法长事务,优先联系业务方,而不是直接 kill
预防比排查更重要:autocommit 和 lock_wait_timeout
MDL 阻塞本质是事务生命周期与元数据变更冲突,预防的关键不在调参,而在控制事务边界和 DDL 执行时机。
- 确保应用连接默认
autocommit = 1,避免隐式事务长期持锁 - DDL 前务必检查:
SELECT * FROM information_schema.innodb_trx WHERE trx_started -
SET GLOBAL lock_wait_timeout = 5可让 DDL 在 5 秒拿不到锁就失败,防止连锁阻塞,但它对已建立的连接无效,且不能替代事务治理
最常被忽略的一点:MDL 锁和事务绑定,不是语句级的。哪怕只是个普通 SELECT,只要事务没提交,它就一直占着读锁——这点在连接池复用场景下极易被低估。











