MySQL出现Waiting for table metadata lock的主因是未提交事务长期持有MDL锁,阻塞DDL及后续所有表操作;常见场景包括长事务、显式BEGIN后遗忘COMMIT、autocommit=OFF连接池滞留等。

为什么 SHOW PROCESSLIST 里会看到 Waiting for table metadata lock
这不是行锁或事务锁,而是元数据锁(MDL),MySQL 在执行 DDL(如 ALTER TABLE、DROP TABLE)前必须获取 MDL_EXCLUSIVE 锁;而任何活跃的 DML/SELECT(哪怕只是 SELECT * FROM t LIMIT 1)在事务中未提交,都会持有 MDL_SHARED_READ 或 MDL_SHARED_WRITE 锁,阻塞后续 DDL 的排他请求。
关键点在于:只要事务没 COMMIT 或 ROLLBACK,哪怕只执行了一条 SELECT,MDL 锁就一直不释放。此时新发起的 ALTER 就卡在 Waiting for table metadata lock 状态,且这个等待会**传染**——后续所有对该表的查询、更新也会被一起堵住。
- 常见诱因是开发/DBA 在客户端(如 DBeaver、MySQL CLI)手动
BEGIN后只查了数据却忘了COMMIT,连接挂着不动 -
autocommit=OFF的连接池配置(如未显式设autocommit=True的aiomysql)也容易导致隐式事务长期滞留 - 长查询(如未加
LIMIT的全表扫描)本身不开启事务,但会持有一个短暂的 MDL_SHARED_READ,在它执行期间 DDL 会被阻塞
KILL 哪个线程才能真正解阻塞
不能只看 State 是 Waiting for table metadata lock 的线程——那是“受害者”,真正要 KILL 的是“持有者”:即那个开启了事务、执行过操作、但至今没提交的连接。
用下面语句定位源头:
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE COMMAND != 'Sleep' AND TIME > 60 ORDER BY TIME DESC;
重点关注 COMMAND 为 Query 或 Sleep 但 TIME 很大的线程,尤其是 INFO 字段显示有 SELECT、UPDATE、BEGIN 等且长时间没变化的。这类线程大概率就是 MDL 持有者。
- 不要
KILL正在alter table的线程——它只是等锁,杀掉后重试还会卡住 - 如果
PROCESSLIST里找不到明显长事务,检查是否还有未关闭的旧连接(比如应用异常退出但连接没回收) - 某些场景下(如两个事务都在
COMMIT阶段),PROCESSLIST可能不体现锁等待,需结合performance_schema.metadata_locks查看(MySQL 5.7+)
innodb_lock_wait_timeout 和 MDL 锁等待是两回事
innodb_lock_wait_timeout 控制的是 InnoDB 行锁等待超时(比如两个事务争同一行),单位秒,默认 50;而 MDL 锁等待没有对应可调参数,它由持有者生命周期决定——你无法通过改配置让 DDL “自动放弃等待”,只能等持有者释放或被干掉。
所以当看到 ERROR 1205 (HY000): Lock wait timeout exceeded,先确认是不是行锁问题;如果是 Waiting for table metadata lock 卡住,则改 innodb_lock_wait_timeout 完全无效。
- 误配
innodb_lock_wait_timeout = 1可能让业务频繁报错回滚,但对 DDL 阻塞毫无帮助 - DDL 卡住时,
SHOW ENGINE INNODB STATUS的TRANSACTIONS部分通常看不到线索,因为 MDL 不属于 InnoDB 层 - 真正影响 DDL 执行时间的是表大小、I/O 负载、online DDL 支持度,不是锁等待参数
如何避免下次再被 MDL 锁卡住
核心原则:不让事务意外“活”太久。生产环境尤其要约束客户端行为和连接池配置。
在应用层:
- 所有数据库连接池初始化时强制设置
autocommit=True(如aiomysql.create_pool(..., autocommit=True)),避免隐式事务 - 禁止在业务代码里手动写
BEGIN/START TRANSACTION,除非明确需要跨多语句一致性 - 对管理类工具(DBeaver、Navicat)启用“执行后自动提交”或“断开连接时自动回滚未提交事务”选项
在运维侧:
- 定期巡检:
SELECT * FROM information_schema.PROCESSLIST WHERE TIME > 300 AND COMMAND != 'Sleep'; - 配置
wait_timeout和interactive_timeout(如设为 300 秒),让空闲连接自动断开 - DDL 操作尽量选在低峰期,并提前用
pt-online-schema-change或 MySQL 8.0+ 的ALGORITHM=INSTANT降低锁表风险
最常被忽略的一点:MDL 锁的持有者未必是“正在运行”的语句,而可能是早已执行完、只差一个 COMMIT 的静默事务——盯住 TIME 列比盯住 STATE 更有效。











