UNDO空间暴涨主因是长事务滞留,需监控history list length并及时处理空闲超60秒的活跃事务;生产环境应先确认事务状态再谨慎KILL,批量操作须分批提交,根治在于应用层优化。

查长事务:别等磁盘报警才动手
UNDO空间暴涨往往不是突然发生的,而是某个事务卡住几分钟甚至几小时后,把所有快照读需要的旧版本数据全堆在UNDO里。这时候history list length会飙升,SELECT变慢,PURGE线程CPU拉满——但问题根源早就在那儿了。
实操建议:
- 用这条语句实时抓活跃且空闲时间长的事务:
SELECT t.*, TO_SECONDS(NOW()) - TO_SECONDS(t.trx_started) AS idle_time FROM INFORMATION_SCHEMA.INNODB_TRX t ORDER BY idle_time DESC LIMIT 5; - 重点看
idle_time超过60秒的,尤其是trx_state = 'RUNNING'但trx_operation_state为空或卡在fetching rows的——大概率是应用没提交,或者被阻塞了没感知 - 配合
SHOW ENGINE INNODB STATUS\G里的TRANSACTIONS部分,确认是否持有锁、是否被其他事务堵住
杀还是不杀:生产环境下的取舍逻辑
发现长事务不能无脑KILL,尤其在金融、订单类场景——它可能正在做关键校验,KILL后业务状态不一致比慢更危险。但放着不管,UNDO撑爆磁盘、主从延迟跳到分钟级,同样不可接受。
实操建议:
- 先查这个事务关联的
PROCESSLIST:SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID = ?;(?替换为trx_id)确认是不是应用连接、有没有正在执行的SQL - 如果
INFO为空、COMMAND是Sleep、TIME远超业务预期(比如>300),基本可判定“挂起”,通知对应业务方后KILL - 如果
INFO是UPDATE ... WHERE ...且TIME持续增长,说明真正在跑大更新——此时应优先联系开发改成分批提交,而不是强杀
分批提交:批量操作的硬性约束
一次性更新10万行,哪怕只花2分钟,也会让UNDO保留这2分钟内所有旧版本数据。MVCC机制下,只要还有并发查询在读这个表,这些UNDO就一个都不能删。
实操建议:
- 把大事务拆成每
1000~5000行为一批,每批后COMMIT;用ROW_COUNT()或应用层计数确保不漏不重 - 避免在事务里混用DDL(如
ALTER TABLE)和DML,DDL会隐式提交,打乱你的分批逻辑 - MySQL 8.0+ 可启用
innodb_strict_mode=ON,防止INSERT ... SELECT这种“看起来像单条”实则生成巨量UNDO的操作绕过检查
监控不能只看大小:history list length才是核心指标
UNDO表空间文件(如ibdata1或独立undo_001)涨到几十GB,未必代表有问题;但history list length长期高于5000,基本就是积压信号——它直接反映待清理UNDO记录数,和PURGE线程压力正相关。
实操建议:
- 定期查:
SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_history_list_length'; - 阈值参考:稳定业务建议
,短时峰值<code>可容忍;持续>10000必须介入 - 注意:这个值不会因为“UNDO表空间自动扩展”而下降——扩展只是给它更多空间堆放垃圾,不解决根本问题
真正难处理的,从来不是“怎么清UNDO”,而是“怎么让业务不产生那么多UNDO”。监控数字、杀事务、调参数,都是救火;把批量逻辑下沉到应用层分批、加事务超时控制、用SELECT ... FOR UPDATE SKIP LOCKED替代全表扫,才是根治点。这些地方一旦漏掉,再调innodb_undo_retention也没用。










