mysql出现waiting for table metadata lock,说明有会话持mdl读锁未释放,而ddl操作在等写锁,导致后续所有dml/dql连锁阻塞;必须定位并终止持有锁的会话,不能只杀等待中的ddl。

直接结论:MySQL 出现 Waiting for table metadata lock,说明有会话持有了 MDL 读锁未释放,而另一个 DDL 操作(如 ALTER TABLE)在等写锁,后续所有对该表的 DML/DQL 都会被连锁阻塞 —— 必须定位并终止持有锁的会话,不能只杀 DDL。
为什么 show processlist 看到的“等待者”不是真凶?
执行 show processlist 时,你大概率看到的是卡在 Waiting for table metadata lock 的 DDL 或长查询,但它只是“受害者”,不是阻塞源。真正持锁的是更早发起、尚未结束的事务(哪怕只是个没提交的 SELECT),它占着 SHARED_READ 锁不放,导致后续所有需要 EXCLUSIVE 锁的操作排队。
-
performance_schema.metadata_locks是唯一能直接看到“谁持有什么类型 MDL 锁”的表,但 MySQL 5.7 默认关闭,需手动启用 - MySQL 8.0+ 默认开启,可直接查;5.7 必须先执行:
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'global_instrumentation'; UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME = 'wait/lock/metadata/sql/mdl'; - 查出来后重点关注
LOCK_TYPE(SHARED_READ/EXCLUSIVE)、LOCK_DURATION(TRANSACTION表示事务级锁,危险)、OWNER_THREAD_ID
如何用 metadata_locks + threads 关联出具体会话?
单查 metadata_locks 只能看到线程 ID,必须关联 performance_schema.threads 才能拿到用户、SQL、连接信息。典型查询如下:
SELECT m.OBJECT_SCHEMA, m.OBJECT_NAME, m.LOCK_TYPE, m.LOCK_DURATION, m.LOCK_STATUS, t.PROCESSLIST_USER, t.PROCESSLIST_HOST, t.PROCESSLIST_DB, t.PROCESSLIST_COMMAND, t.PROCESSLIST_TIME, t.PROCESSLIST_STATE, t.PROCESSLIST_INFO FROM performance_schema.metadata_locks m JOIN performance_schema.threads t ON m.OWNER_THREAD_ID = t.THREAD_ID WHERE m.OBJECT_SCHEMA = 'your_db' AND m.OBJECT_NAME = 'your_table' AND m.LOCK_STATUS = 'GRANTED' ORDER BY t.PROCESSLIST_TIME DESC;
- 重点看
PROCESSLIST_INFO字段:如果是SELECT ... SLEEP(3600)或空值(表示空闲但事务未提交),基本就是它 -
PROCESSLIST_COMMAND = 'Sleep'且PROCESSLIST_TIME > 60,极大概率是连接池未 close 或应用忘记 commit - 注意区分:
PROCESSLIST_INFO为空 ≠ 没在执行 SQL,可能是事务中只执行了 BEGIN 就挂起
KILL 哪个 PID?为什么不能只杀 DDL?
查到持锁会话的 PROCESSLIST_ID 后,执行 KILL <pid></pid>。关键点在于:
- DDL 进程(如
ALTER TABLE)本身不持锁,它在等锁,杀它只会让下一个 DDL 继续等 —— 阻塞链还在 - 真正要杀的是
LOCK_DURATION = 'TRANSACTION'且PROCESSLIST_COMMAND != 'Query'的那个会话,通常是Sleep或Connect - 如果多个会话对同一张表持有
SHARED_READ,需全部 kill;但优先杀PROCESSLIST_TIME最大的那个(最老的未提交事务) - MySQL 5.7 中,
threads.PROCESSLIST_ID和information_schema.processlist.ID是一致的,可直接用于KILL
容易被忽略的兼容性与副作用
启用 metadata_locks 监控本身有成本,且不同版本行为差异大:
- MySQL 5.7 开启后,每个 MDL 事件都会记录,高并发下可能影响 performance_schema 性能,建议只在排查期开启,问题解决后可关
- MySQL 8.0.14+ 新增
performance_schema.metadata_locks的OWNER_EVENT_ID字段,可追溯到具体 SQL 语句,但需配合events_statements_current查原始语句 - 某些 ORM(如 Django、SQLAlchemy)或连接池(如 DBUtils、aiomysql)默认不自动 commit,即使执行了
SELECT也会开启隐式事务并长期持锁 —— 这类问题不会在innodb_trx里显示活跃事务,但会在metadata_locks显形 -
KILL持锁会话可能导致客户端报错(如Lost connection to MySQL server during query),应用层需有重试逻辑
真正难的不是定位,而是确认那个 Sleep 状态的会话到底属于哪个业务模块——它可能来自一个早已没人维护的定时脚本,或者某个开发环境遗留的未关闭连接。查 PROCESSLIST_USER 和 PROCESSLIST_HOST 比查 SQL 更有效。











