
为什么 SHOW PROCESSLIST 里总卡着 Waiting for table metadata lock
这不是锁表,是元数据锁(MDL)在等一个“读写互斥”的时机。典型场景是:有人在执行 ALTER TABLE、DROP INDEX 这类 DDL,而另一堆慢查询或长事务正拿着表的 MDL 读锁不放——DDL 必须等所有读锁释放才能加写锁,于是堵住。
常见错误现象:SELECT 看似简单却卡住;KILL 掉一个线程后,另一个立刻顶上卡住;information_schema.PROCESSLIST 显示状态长期不动。
- 优先查
INFORMATION_SCHEMA.INNODB_TRX,看有没有运行超 10 秒的trx_state = 'RUNNING'且trx_started时间很早的事务 - 再用
SELECT * FROM performance_schema.threads WHERE PROCESSLIST_INFO LIKE '%ALTER%';找出正在等锁的 DDL 线程 - 注意:普通
SELECT不显式开启事务,但若连接没设autocommit=1,第一条SELECT就会隐式开启事务并持有 MDL 读锁,直到COMMIT或连接断开
怎么快速定位哪个事务在 hold 住 MDL
MySQL 5.7+ 的 performance_schema 是唯一靠谱来源,SHOW PROCESSLIST 只显示当前动作,不反映锁持有关系。
实操建议:
- 打开
performance_schema(确认setup_consumers中events_statements_current和events_transactions_current为YES) - 执行:
SELECT t.THREAD_ID, t.PROCESSLIST_ID, t.PROCESSLIST_USER, t.PROCESSLIST_HOST, t.PROCESSLIST_INFO, trx.trx_started, trx.trx_state 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) > 30;
- 重点看
PROCESSLIST_INFO是否为空(空表示事务已开始但没执行语句),以及trx_started时间戳是否异常久远
KILL 哪个线程才真正解堵
不能只杀 Waiting for table metadata lock 的那个线程——它只是受害者。要杀的是持有 MDL 读锁、又迟迟不提交的事务线程,通常是那个看似“安静”的 SELECT 或空闲连接。
容易踩的坑:
-
KILL CONNECTION比KILL QUERY更彻底,后者只中断当前语句,前者才真正释放事务和锁 - 如果被杀线程正在执行大事务回滚(比如刚
INSERT百万行后ROLLBACK),KILL 后反而会更卡——因为回滚本身也要持锁 - 应用层连接池未设置
maxLifetime或idleTimeout,导致空闲连接长期挂着事务,这种连接最隐蔽也最危险
如何避免下次又被 MDL 锁死
DDL 不该在业务高峰期跑,但更关键的是控制事务粒度和连接行为。
实操建议:
- 所有应用连接初始化时强制执行
SET autocommit = 1,杜绝隐式事务 - ORM 框架(如 MyBatis、Hibernate)检查是否启用了
autoCommit=false的 DataSource 配置 - 对必须的 DDL,用
pt-online-schema-change或gh-ost,它们能绕过 MDL 写锁(原理是双写+触发器,不阻塞读) - 监控项加一条:
SELECT COUNT(*) FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60,告警阈值设为 1
真正难处理的不是锁本身,而是那些没日志、没监控、连 PROCESSLIST_INFO 都为空的“幽灵事务”——它们往往来自客户端异常断连但服务端未及时清理,或者连接池复用后忘记重置事务状态。










