show processlist 中出现“waiting for table metadata lock”是因为元数据锁(mdl)阻塞:长事务或未提交ddl持有表的mdl,导致后续所有访问该表的操作(包括select)排队等待。

为什么 SHOW PROCESSLIST 里总卡着 Waiting for table metadata lock
这是 MySQL 5.5+ 引入元数据锁(MDL)后最典型的阻塞现象:一个长事务或未提交的 DDL 正拿着表的 MDL,其他所有想访问该表的语句(哪怕只是 SELECT)都得排队等。不是锁行、也不是锁表,是锁“表结构定义”本身。
关键点在于:MDL 是语句执行前就加上的,且会一直持有到事务结束(即使语句早已执行完)。所以一个 BEGIN; SELECT * FROM t1; 不提交,后续对 t1 的任何操作都会被拦住。
- 常见诱因:
ALTER TABLE、DROP TABLE、RENAME TABLE等 DDL;长时间运行的SELECT或未提交的UPDATE - 注意:
SELECT也会加 MDL(共享锁),但和 DDL 的排他锁冲突 —— 这就是只读查询也被卡住的原因 - DDL 操作默认在
autocommit=1下执行,看似“单条”,实则内部仍需获取排他 MDL,期间会等所有已有共享锁释放
怎么快速定位谁在 hold 住 MDL
靠 SHOW PROCESSLIST 只能看到“等待者”,看不到“持有者”。必须查 performance_schema 的锁视图(MySQL 5.6+ 默认开启):
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_DURATION, LOCK_STATUS, OWNER_THREAD_ID FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table';
重点关注 LOCK_STATUS = 'PENDING'(等待中)和 LOCK_STATUS = 'GRANTED'(已持有)的记录,再用 OWNER_THREAD_ID 去 performance_schema.threads 查对应线程的 SQL 和状态。
- 如果
performance_schema关闭了,只能靠SHOW ENGINE INNODB STATUS\G翻 TRANSACTIONS 部分,但看不到 MDL 细节,仅能推测 -
information_schema.INNODB_TRX能看到活跃事务,但无法直接关联到 MDL 持有者 —— 别指望它直接给出答案 - 别依赖
PROCESSLIST的Time字段判断“谁更老”,因为等待时间不等于持有时间
KILL 哪个线程才真正解围
必须 KILL 持有排他锁(或长时间占着共享锁不释放)的那个线程,而不是一堆在等的线程 —— 杀错对象只会让等待队列更长。
- 优先杀 DDL 线程:比如正在执行
ALTER TABLE t1 ADD COLUMN x INT的线程,它拿的是排他 MDL,影响最大 - 其次考虑长事务中的第一个语句线程:例如一个跑了 20 分钟的
BEGIN; UPDATE t1 ...;,哪怕当前没在执行,MDL 仍被绑定在事务上 - 不要杀
Waiting for table metadata lock状态的线程 —— 它们没锁,杀了也没用,还会重连重试,继续排队 -
KILL后 DDL 会回滚(如ALTER),事务会中断,业务侧需处理失败逻辑
怎么避免线上频繁触发 MDL 等待
根本思路是缩短 MDL 持有时长 + 隔离高风险操作。没有银弹,但有几个硬约束必须遵守:
- DDL 操作务必在低峰期执行,且提前确认没有长事务在操作目标表(用
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60;扫描) - 应用层禁止在事务里执行无关的
SELECT,尤其是跨表、带子查询的“大查询”,它们会把 MDL 锁住整个事务周期 - MySQL 5.7+ 可开启
lock_wait_timeout(默认 31536000 秒),但它是针对锁等待超时,对 MDL 无效;真正有用的是wait_timeout和interactive_timeout控制空闲连接自动断开 - 对于大表 DDL,优先用
pt-online-schema-change或 MySQL 8.0 的ALGORITHM=INSTANT(仅限部分变更),它们能绕过传统 MDL 排他锁
MDL 的设计本意是保证 DDL 和 DML 的强一致性,但它把“安全”变成了“脆弱”。真正难的不是查锁,而是让开发和 DBA 对“事务边界”和“DDL 影响面”有同一认知 —— 很多问题,其实发生在 BEGIN 那一行之前。










