快速定位并杀掉阻塞ALTER TABLE的长事务:先查information_schema.INNODB_TRX中运行超60秒的事务,再通过PROCESSLIST确认对应线程SQL,最后用KILL(非KILL QUERY)终止;避免方法包括应用显式控制事务、设置超时参数、每日巡检告警及升级前真实预演pt-osc。

长事务导致 ALTER TABLE 卡住,怎么快速定位和杀掉?
MySQL 升级常涉及表结构变更,而 ALTER TABLE 会被活跃的长事务阻塞,本质是元数据锁(MDL)等待。不是所有“慢”都是 IO 或 CPU 问题,很可能是某个没提交的事务在后台挂着。
- 查看当前持有 MDL 写锁且未提交的事务:
SELECT trx_id, trx_mysql_thread_id, trx_started, trx_state FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;
- 关联线程信息查 SQL:
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE ID = ?;
(把上一步的trx_mysql_thread_id填进去) - 确认无业务影响后,用
KILL ?干掉线程(注意:不是KILL QUERY,必须是KILL才能释放 MDL)
别直接杀 sleep 状态的线程——它可能刚执行完 UPDATE 正等着应用发 COMMIT;重点盯 trx_state = 'RUNNING' 或长时间 'LOCK WAIT' 的。
如何避免升级前突然冒出长事务?
很多团队在升级窗口前才检查,结果发现定时任务、ETL 脚本或监控探针悄悄启了事务却忘了提交。这不是偶然,是默认行为惯性。
- 应用层必须显式控制事务边界:禁止在循环里开事务、禁止跨 HTTP 请求复用连接并隐式延续事务
- MySQL 层加兜底:设置
wait_timeout和interactive_timeout(建议 ≤ 300),但注意这不杀活跃事务,只断连;真正管用的是max_execution_time(8.0+)或innodb_lock_wait_timeout配合应用重试 - 每日巡检脚本里固定跑:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 300;
,超过 5 分钟就告警
有些 ORM(比如旧版 Django)在 autocommit=False 模式下,SELECT 后不 COMMIT 也会持 MDL 读锁——升级前务必确认框架事务模型。
pt-online-schema-change 为什么有时也卡住?
它本意是绕过 MDL,但前提是原表不能有长事务、不能有未完成的 DDL、也不能有触发器干扰。一旦卡住,往往不是工具问题,而是环境没清理干净。
- 卡在
Copying rows阶段?先查information_schema.PROCESSLIST里有没有状态为Sending data且INFO是INSERT ... SELECT的长耗时线程——这说明复制本身被阻塞,根源还是源表有未释放的 MDL - 卡在
Swapping tables?这是最关键的原子操作,需要对原表和新表同时加 MDL 写锁。此时任何对原表的SELECT(哪怕只是SELECT COUNT(*))、INSERT、UPDATE都会排队等锁,而它们又反过来阻塞 swap - 不要用
--dry-run代替真实预演:它不申请 MDL,测不出真实锁冲突
如果业务无法停写,pt-osc 的 --max-load 和 --critical-load 参数得调保守些,否则复制线程抢不过业务写入,拖得越久,撞上长事务的概率越高。
升级后发现某些表 information_schema.TABLES 里的 UPDATE_TIME 是 NULL?
这不是 bug,是 8.0+ 的正常行为。从 8.0 开始,UPDATE_TIME 默认不再维护,除非你手动开启 innodb_stats_on_metadata = ON(不推荐,有性能开销)。但这会影响你用时间戳判断“哪些表最近被改过”的运维逻辑。
- 别依赖
UPDATE_TIME做升级校验;改用performance_schema.table_io_waits_summary_by_table查近期访问,或靠 binlog 位点比对 - 如果真要恢复该字段,需重启 mysqld 并设置
innodb_stats_on_metadata = ON,但上线后记得关——它会让每次SHOW TABLE STATUS都触发统计更新,高并发下明显拖慢
元数据锁冲突从来不是孤立事件,它总暴露底层事务治理的松动。最麻烦的不是怎么 kill,而是为什么总有事务不肯结束。










