真正要杀的是持锁事务而非等待线程;需通过performance_schema.metadata_locks定位lock_status='granted'的sleep连接,优先kill query再kill以避免长回滚。

DDL卡在Waiting for table metadata lock时,真正要杀的不是那个“等待中”的线程,而是背后持锁的事务——它可能正安静地Command = 'Sleep',却死死攥着SHARED_WRITE锁不放。
查performance_schema.metadata_locks定位真实持锁者
MySQL 5.7+ 的 MDL 持有关系不会出现在 SHOW PROCESSLIST 的 State 字段里,必须靠 performance_schema.metadata_locks 查。先确认它已启用:
-
SELECT @@performance_schema;返回1才有效 -
SELECT * FROM performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl';中ENABLED和TIMED都应为YES;若否,执行:UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME = 'wait/lock/metadata/sql/mdl';
再查具体锁:
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 就是真正在持锁的连接 ID。
识别Command = 'Sleep'却仍在 RUNNING 的悬挂事务
最常卡住 DDL 的,不是正在跑慢查询的线程,而是 Command = 'Sleep'、State 为空、但 Time 超过 300 秒的连接。它往往对应一个 BEGIN 了却没 COMMIT 或 ROLLBACK 的事务,MDL 锁从 BEGIN 开始就一直挂着。
直接执行:SHOW FULL PROCESSLIST;,重点关注三列组合:
-
Command为Sleep -
State为NULL或空 -
Time > 300(秒)
这类线程 ID 就是首要怀疑对象。补充验证方式(避免误杀):
- 查
information_schema.INNODB_TRX,确认该线程 ID 是否出现在trx_mysql_thread_id中,且trx_state = 'RUNNING' - 查
performance_schema.metadata_locks,过滤OWNER_THREAD_ID匹配该线程 ID,看是否持有SHARED_WRITE或EXCLUSIVE锁 - 若
mysql tables in use和locked tables均为 0(通过SHOW ENGINE INNODB STATUS\G的TRANSACTIONS部分查),基本可判定无实际数据操作,仅持锁空转
为什么不能直接 KILL,而要先 KILL QUERY
对一个挂起的事务连接执行 KILL <thread_id></thread_id> 会强制断开连接并回滚整个事务,看似干脆,但有风险:
- 若该连接正被应用连接池复用,突然断连可能触发连接池异常重连逻辑,造成短暂抖动
- 若事务已写入大量 undo 日志,回滚本身可能耗时数分钟,期间仍阻塞其他 DDL
- 某些 ORM 框架(如 Hibernate)在事务异常中断后可能残留脏状态,影响后续请求
更稳妥的做法是分两步:
-
KILL QUERY <thread_id></thread_id>→ 中断当前语句(对Sleep连接效果等同于无操作,但安全) - 观察 10–20 秒,若
State仍未变,再执行KILL <thread_id></thread_id>
注意:KILL QUERY 对纯 Sleep 连接无效,但它是个“试探动作”——若连接真在执行长查询,这一步就能提前释放 MDL,避免升级到 KILL。
真正容易被忽略的是:锁本身不显眼,但它的生命周期完全绑定在事务上;一个没提交的 SELECT 和一个跑了十分钟的 UPDATE 在 MDL 层面地位完全一样——都会长期持有锁。排查时别只盯着 State = 'Sending data' 或 'Copying to tmp table',那些安静的 Sleep 才是最危险的。











