真正要杀的是持锁的“安静事务”而非等待线程;需用INNODB_TRX与metadata_locks交叉定位Sleep但trx_state='RUNNING'的悬挂事务,先KILL QUERY再KILL连接,并调低wait_timeout预防。

DDL卡在Waiting for table metadata lock,先别KILL等待线程
真正要杀的不是那个显示Waiting for table metadata lock的线程,而是背后持锁却不释放的“安静事务”。MySQL 8.0升级后,这类悬挂事务更暴露——它们在SHOW PROCESSLIST里状态是Sleep、INFO为空、Time可能高达几小时,但在INFORMATION_SCHEMA.INNODB_TRX中仍显示trx_state = 'RUNNING'。
常见来源包括:
- Python/Java应用用
pymysql或aiomysql连接,默认autocommit=False,执行完SELECT没commit()也没close() - DBA手动
BEGIN后去干别的事,连接空闲但事务未结束 -
mysqldump --single-transaction进程未退出,对所有表持SHARED_READ锁
用INNODB_TRX和metadata_locks交叉定位持锁连接
仅查SHOW PROCESSLIST找不到真凶,必须组合两个视图:
第一步,查疑似悬挂事务:
SELECT trx_id, trx_started, trx_state, trx_query FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_query IS NULL ORDER BY trx_started ASC LIMIT 5;
第二步,关联performance_schema.metadata_locks确认它是否正对目标表持锁:
SELECT m.OBJECT_SCHEMA, m.OBJECT_NAME, m.LOCK_TYPE, m.LOCK_STATUS,
t.PROCESSLIST_ID, t.PROCESSLIST_USER, t.PROCESSLIST_HOST
FROM performance_schema.metadata_locks m
JOIN performance_schema.threads t ON m.OWNER_THREAD_ID = t.THREAD_ID
WHERE m.OBJECT_SCHEMA = 'your_db'
AND m.OBJECT_NAME = 'your_table'
AND m.LOCK_STATUS = 'GRANTED'
AND m.LOCK_TYPE IN ('SHARED_READ', 'SHARED_WRITE');
把上一步查出的trx_id和这一步的PROCESSLIST_ID对上,就锁定了该杀的线程ID。
KILL操作要分两步:先KILL QUERY,再KILL
直接KILL <thread_id>可能触发长回滚,尤其当事务已修改大量行时。稳妥做法是:
- 先执行
KILL QUERY <thread_id>(对Sleep连接无效,但安全) - 等10–20秒,观察
INNODB_TRX是否消失、metadata_locks中对应GRANTED行是否清除 - 若无变化,再执行
KILL <thread_id>
注意:KILL QUERY不会中断事务本身,只终止当前语句;而KILL会强制回滚整个事务,代价更高。
升级后最易被忽略的配置陷阱
MySQL 8.0默认wait_timeout = 28800(8小时),而5.7下很多客户端空闲超时断连更快。这意味着旧版“自动清理”的悬挂事务,在8.0里能挂得更久、卡得更死。
预防动作建议立即做:
- 检查所有应用连接池配置,确保
autocommit=True或显式commit()调用 - 设置
interactive_timeout和wait_timeout为600–1800秒(10–30分钟) - 禁用
mysqldump --single-transaction在业务高峰期运行
DDL卡住时,最危险的操作是凭Time值盲目KILL——一个Time=3600的Sleep线程,可能是刚连上来执行了SELECT就空闲了,也可能是BEGIN了两小时没提交的真凶。必须靠INNODB_TRX.trx_state和metadata_locks.LOCK_STATUS双验证。


















