出现waiting for table metadata lock时,ddl只是受害者,真正持锁者是持有shared_read/shared_write锁的会话,需通过performance_schema.metadata_locks定位processlist_id,并结合innodb_trx确认sleep中仍running的未提交事务。

直接结论:出现 Waiting for table metadata lock 时,DDL 操作本身只是“受害者”,真正要找的是持有 S 或 SHARED_READ 类型 MDL 锁的会话——它大概率是一个没提交的事务、一个卡住的 SELECT、或一个正在跑的 mysqldump。
怎么快速定位持锁线程(MySQL 5.7+)
别只看 SHOW PROCESSLIST,它根本不会显示谁在持 MDL 锁。必须查 performance_schema.metadata_locks:
- 先确认采集器已启用:
SELECT * FROM performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl';,确保ENABLED和TIMED都是YES - 查具体锁关系(替换
your_db和your_table):SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, PROCESSLIST_ID, PROCESSLIST_USER, PROCESSLIST_HOST FROM performance_schema.metadata_locks m JOIN performance_schema.threads t ON m.OWNER_THREAD_ID = t.THREAD_ID WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table' AND LOCK_STATUS = 'GRANTED';
- 重点关注
LOCK_TYPE是SHARED_READ或SHARED_WRITE的行,对应PROCESSLIST_ID就是持锁者
为什么持锁者看起来“什么都没干”?
最常见的持锁者,其实是 Command = 'Sleep'、Info 为空、但 Time 已几百秒的连接。它往往对应一个显式开启却未提交的事务:
- 执行过
BEGIN或START TRANSACTION,然后只做了个SELECT就挂了 - 应用层用了连接池但没正确关闭事务(比如 Python 的
conn.commit()忘了,或 Java 的Connection.close()没触发事务结束) - 甚至只是 DBA 在客户端敲完
BEGIN就去开会了,事务一直开着 - 验证方法:用上一步拿到的
PROCESSLIST_ID查information_schema.INNODB_TRX,确认trx_state = 'RUNNING'且trx_started时间很早
哪些操作会悄悄拿 MDL 锁?
不是只有 UPDATE 或 INSERT 才会持锁,这些看似“只读”的操作同样危险:
-
SELECT在REPEATABLE READ隔离级别下,第一次执行就会对表加SHARED_READMDL 锁,直到事务结束 -
mysqldump --single-transaction会为所有 dump 表加SHARED_READ锁,dump 没结束,锁就不放 - 查询
INFORMATION_SCHEMA.TABLES等系统表,也可能触发短暂但关键的 MDL 请求,高并发下易成瓶颈 -
TRUNCATE TABLE、DROP VIEW、ALTER TABLE这些 DDL 需要EXCLUSIVE锁,和上面所有SHARED_*锁互斥
杀错线程会雪上加霜
绝对不要直接 KILL 显示 Waiting for table metadata lock 的那个线程——它只是排队等锁,杀掉后重试还是卡住:
- 优先
KILL QUERY持锁线程(比如那个 Sleep 中的连接),避免触发大事务回滚阻塞 - 如果
KILL QUERY不生效(比如线程正在执行不可中断操作),再考虑KILL整个连接 - 更稳妥的做法是先设
innodb_lock_wait_timeout缩短等待时间,或改用ALGORITHM=INSTANT(支持的 DDL 类型)绕过 MDL 排队
真正麻烦的从来不是锁本身,而是那些没人记得自己开了事务、也没人知道它还活着的“幽灵连接”。











