根本原因是高并发下未及时释放的mdl锁被放大暴露——一个begin后未commit的连接持有mdl_shared_write锁,会阻塞后续所有ddl和同表查询;高负载加速暴露autocommit=off缺陷,监控工具、mysqldump等易成静默持锁源;定位需查长时间running且info为空的事务,优先kill query再视情况kill。

根本原因不是高负载本身,而是高并发下未及时释放的 MDL 锁被放大暴露了——只要有一个连接 BEGIN 后忘了 COMMIT,它持有的 MDL_SHARED_WRITE 就会卡住所有后续 DDL 和同表查询。
为什么高负载会让 Metadata Lock 问题突然爆发
低流量时,一个没提交的事务可能几小时都没人碰那张表;但高负载下,DDL(如加字段)、备份脚本、监控查询、应用缓存刷新全挤在一张表上,等待链立刻形成。典型表现是:SHOW PROCESSLIST 里一堆线程卡在 Waiting for table metadata lock,且 Time 值持续上涨,新查询也迅速加入等待队列。
这不是锁争用变多了,而是“一个悬挂事务 + 多个并发请求”触发了 MDL 的传染性阻塞:DDL 等不到排他锁 → 后续所有对该表的 SELECT/INSERT 也等不到共享锁 → 连接池快速耗尽。
- 高负载本身不产生 MDL,但会加速暴露
autocommit=OFF的连接池配置缺陷 - 监控工具(如 pt-heartbeat、Zabbix 的表检查)常隐式开启事务,若超时未清理,就成了静默持锁源
-
mysqldump 默认加
--single-transaction,但在REPEATABLE READ下仍会为每个表获取一次MDL_SHARED_READ,长 dump 过程中 DDL 就会被卡住
如何快速定位那个“安静”的持锁连接
别只盯着 State = 'Waiting for table metadata lock' 的线程——那是受害者。真凶往往 Command 是 Sleep、Info 为空、Time > 300 的连接,它可能只是执行过一条 SELECT * FROM users LIMIT 1 就再没动过。
执行这条 JOIN 查询直接揪出嫌疑对象:
SELECT t.PROCESSLIST_ID, t.PROCESSLIST_USER, t.PROCESSLIST_HOST,
trx.trx_started, trx.trx_state, trx.trx_isolation_level
FROM performance_schema.threads t
JOIN information_schema.INNODB_TRX trx
ON t.THREAD_ID = trx.trx_mysql_thread_id
WHERE trx.trx_state = 'RUNNING'
AND UNIX_TIMESTAMP() - UNIX_TIMESTAMP(trx.trx_started) > 300;
- 结果里
PROCESSLIST_INFO为空,基本可断定是 BEGIN 未提交的悬挂事务 - 若
trx_isolation_level是REPEATABLE READ,哪怕只读事务也会全程持有 MDL - 注意比对
PROCESSLIST_HOST:如果是应用服务器 IP,优先联系对应服务负责人确认是否正在调试
为什么不能直接 KILL,而要先 KILL QUERY
对一个纯 Sleep 的持锁连接执行 KILL <id></id>,MySQL 会强制回滚整个事务。如果该事务已写入大量 undo 日志(比如之前执行过大批量 UPDATE),回滚本身可能持续数分钟,期间依然阻塞 DDL,甚至拖垮主从延迟。
- 第一步永远是
KILL QUERY <id></id>:对 Sleep 连接虽无实际效果,但它是安全探针——若连接其实正在执行长查询,这一步就能立即中断并释放 MDL - 等待 10–20 秒,再查
INNODB_TRX:如果trx_state变成ROLLING BACK,说明回滚已启动,需耐心等;如果仍是RUNNING,再执行KILL <id></id> - 避免误杀:KILL 前用
SELECT * FROM performance_schema.metadata_locks WHERE OWNER_THREAD_ID = <thread_id></thread_id>确认它确实在持SHARED_WRITE或EXCLUSIVE锁
真正容易被忽略的三个点
一是 performance_schema.metadata_locks 默认在 MySQL 5.7 中可能未启用:必须确认 setup_instruments 里 wait/lock/metadata/sql/mdl 的 ENABLED 是 YES,否则所有排查都看不到持锁关系。
二是 Python/Java 应用里 cursor.execute("SELECT ...") 后没调 cursor.close(),连接被连接池复用,但事务状态和 MDL 锁不会自动清除——这种“幽灵事务”最难发现。
三是 lock_wait_timeout 默认值是 31536000(1 年),意味着 DDL 会无限期等待。线上建议设为 5 或 10,让失败明确暴露,而不是无声堆积。











