waiting for table metadata lock 是结果而非源头,真正阻塞 ddl 的通常是未提交事务(含仅执行 select 的隐式事务);应优先查 select * from sys.schema_table_lock_waits\g 定位 waiting_pid 与 blocking_pid,并结合 information_schema.innodb_trx 和 performance_schema.metadata_locks 确认持锁事务及 sql。

直接结论:Waiting for table metadata lock 不是源头,而是结果;真正卡住 DDL 的,几乎总是某个没提交的事务(哪怕只是个 SELECT)。
查 sys.schema_table_lock_waits 快速定位谁在等、谁在堵
这是 MySQL 5.7+ 最省力的起点,一行 SQL 就能看清锁等待链:
SELECT * FROM sys.schema_table_lock_waits\G
输出里重点关注三列:waiting_pid(被卡住的线程)、blocking_pid(真正持锁的线程)、sql_kill_blocking_connection(可直接复制执行的 KILL 命令)。
常见陷阱:
- 如果
sys库不可用或权限不足,这个视图会报错或返回空——别硬试,立刻切到performance_schema.metadata_locks方案 - 该视图只显示“已形成等待”的关系,对刚启动还没触发等待的长事务不敏感
看 performance_schema.metadata_locks 区分 GRANTED 和 PENDING
需要先确认 instrumentation 已启用(MySQL 5.7+ 默认开,但有些生产环境被手动关过):
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME = 'wait/lock/metadata/sql/mdl';
然后查锁状态:
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID FROM performance_schema.metadata_locks WHERE LOCK_STATUS IN ('GRANTED', 'PENDING');
GRANTED 表示当前正持有锁(尤其是 SHARED_READ 类型),PENDING 表示正在排队等锁。关键动作是把 OWNER_THREAD_ID 关联到 performance_schema.threads 查出对应 PROCESSLIST_ID 和 PROCESSLIST_INFO:
SELECT t.PROCESSLIST_ID, t.PROCESSLIST_USER, t.PROCESSLIST_HOST, t.PROCESSLIST_INFO FROM performance_schema.threads t JOIN performance_schema.metadata_locks m ON t.THREAD_ID = m.OWNER_THREAD_ID WHERE m.LOCK_STATUS = 'GRANTED' AND m.OBJECT_SCHEMA = 'your_db' AND m.OBJECT_NAME = 'your_table';
注意:LOCK_DURATION 为 TRANSACTION 才代表锁会持续到事务结束;STATEMENT 级别的锁通常秒级释放,不用管。
找未提交事务:别只盯 trx_mysql_thread_id,要看 trx_started
information_schema.innodb_trx 是核心线索表,但光扫 trx_mysql_thread_id 容易漏掉“安静的凶手”:
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx WHERE trx_state = 'RUNNING' ORDER BY trx_started ASC;
重点不是 ID 多大,而是 trx_started 时间是否远早于当前时间(比如 30 分钟前)。很多阻塞来自以下场景:
- 应用连接池复用连接后,事务没显式
COMMIT或ROLLBACK,trx_query可能为空或只显示BEGIN - DBA 在客户端执行了
BEGIN后中断操作,忘了ROLLBACK - Python/Java 脚本中
cursor.execute()后没调conn.commit(),连接被归还池中但事务仍活跃
查到可疑 trx_mysql_thread_id 后,反查它实际在跑什么:
SELECT THREAD_ID, PROCESSLIST_INFO FROM performance_schema.threads t JOIN performance_schema.events_statements_current e USING (THREAD_ID) WHERE t.PROCESSLIST_ID = ?;
KILL 前必须确认:这个线程真的没业务价值吗?
KILL 是最快解法,但也是最危险操作。务必交叉验证:
- 用
SHOW PROCESSLIST看对应PROCESSLIST_ID的State:如果是Sleep且Time很大,大概率是闲置连接里的未提交事务 - 检查
PROCESSLIST_INFO是否为空或仅含BEGIN—— 这类线程基本可以安全 KILL - 避免 KILL 应用连接池中的活跃连接(如
User显示为app_user且Host是应用服务器 IP),优先 KILL DBA 自己的连接或测试脚本
真正容易被忽略的是:MDL 锁持有者可能根本不显示任何正在执行的 SQL,它的 Info 字段为空,State 是 Sleep,只靠 Time 判断会误以为它已空闲——必须结合 innodb_trx.trx_started 和 metadata_locks.LOCK_DURATION 才能确认它还在事务中挂着 S 锁。











