真正要kill的是blocking_pid对应的持锁连接,而非显示“waiting for table metadata lock”的等待线程;优先用kill query,查sys.schema_table_lock_waits可直接获取阻塞链。

直接结论:Waiting for table metadata lock 不是表被锁死,而是某个连接正拿着 MDL 锁没释放;真正要 kill 的不是显示“等待中”的线程,而是 blocking_pid 或 PROCESSLIST_ID 对应的持锁连接,且优先用 KILL QUERY 而非 KILL。
查 sys.schema_table_lock_waits 快速定位阻塞链
这是 MySQL 5.7+ 最省力的入口,它把“谁堵了谁”一次性列清楚,不用拼表、不靠猜:
-
blocking_pid是真正在持锁的连接 ID,waiting_pid是被卡住的 DDL 或查询 -
sql_text字段能直接看到持锁者最后执行的语句(比如一个没提交的SELECT) - 如果返回为空,先确认
performance_schema已启用:SELECT @@performance_schema;必须为 1 - 再检查采集器是否打开:
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME = 'wait/lock/metadata/sql/mdl'; -
blocking_pid为NULL并不表示没锁——很可能是隐式事务(如BEGIN后只跑了一条SELECT就断开)在持锁
联合 INNODB_TRX 和 PROCESSLIST 找“Sleep 却 RUNNING”的悬挂事务
大量 MDL 阻塞源不是正在跑 SQL 的线程,而是那种 COMMAND = 'Sleep'、STATE 为空、但事务状态仍是 RUNNING 的连接。它从 BEGIN 开始就一直占着 MDL_SHARED_READ 锁。
- 必须用
JOIN查,单查INNODB_TRX会漏掉已断连但事务未清理的连接 - 重点关注:
TIME > 300且INFO为空 → 极大概率是应用异常中断或忘记COMMIT -
trx_isolation_level = 'REPEATABLE READ'时,哪怕只执行过一条SELECT,事务一启就持 MDL 锁 - 执行这句:
SELECT t.trx_id, t.trx_started, t.trx_state, t.trx_isolation_level, p.ID, p.USER, p.HOST, p.DB, p.COMMAND, p.TIME, p.STATE, p.INFO FROM information_schema.INNODB_TRX t JOIN information_schema.PROCESSLIST p ON t.trx_mysql_thread_id = p.ID WHERE p.COMMAND = 'Sleep' AND t.trx_state = 'RUNNING' ORDER BY t.trx_started;
用 performance_schema.metadata_locks 确认锁类型和持有者线程
SHOW PROCESSLIST 根本不显示谁在 hold MDL 锁,因为它是服务层锁,得靠这个表看真实状态:
- 先确认采集器已开:
SELECT * FROM performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl';,确保ENABLED和TIMED都是YES - 查具体锁(替换
your_db和your_table):SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, PROCESSLIST_ID, PROCESSLIST_USER, PROCESSLIST_HOST FROM performance_schema.metadata_locks m JOIN performance_schema.threads t ON m.OWNER_THREAD_ID = t.THREAD_ID WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table'; - 重点看
LOCK_STATUS = 'GRANTED'且LOCK_TYPE是SHARED_READ或SHARED_WRITE的行 —— 它们的PROCESSLIST_ID就是持锁者 - 注意:
LOCK_TYPE = 'EXCLUSIVE'的持锁者极少见,通常是正在执行 DDL 的线程本身,此时要杀的是前面那个读锁持有者
为什么 KILL QUERY 比 KILL 更安全
对持锁连接直接 KILL 可能触发长回滚(尤其大事务),而 KILL QUERY 只中断当前语句,保留连接和事务上下文,让应用有机会重试或显式 ROLLBACK:
-
KILL QUERY 12345;中断该线程当前正在执行的语句(如一个慢SELECT),但连接仍存活,事务状态不变 -
KILL 12345;会强制断开连接,MySQL 自动回滚整个事务,若事务已修改大量数据,回滚可能持续数分钟 - 如果应用层没做事务兜底(比如没捕获异常后
ROLLBACK),KILL QUERY后连接仍处于Sleep+RUNNING状态,MDL 锁还在 —— 这时候才需要KILL - 生产环境建议先
KILL QUERY,观察 10 秒,再决定是否KILL
最易被忽略的一点:MDL 锁生命周期绑定事务,不是语句级。哪怕只是普通 SELECT,只要事务没结束,它就一直占着读锁;而 DDL 需要写锁,两者互斥。所以排查永远要回到事务源头,而不是盯着“等待中”的那条 DDL。











