必须先杀掉trx_state='RUNNING'且trx_query IS NULL的最老事务,否则调参、截断或purge加速均无效;需用INNODB_TRX按trx_started排序定位,关联PROCESSLIST确认异常连接,分状态KILL后加大innodb_purge_batch_size并启用undo截断。

查出卡住Purge的长事务
History list length持续高于5000,基本说明Undo在堆积;但真正拦住Purge线程的,往往是没提交或已中断的事务。光看SHOW ENGINE INNODB STATUS\G里的---TRANSACTION 123456789, ACTIVE 3600 sec还不够——得定位到具体线程ID。
执行以下语句快速筛选可疑事务:
SELECT trx_mysql_thread_id, trx_state, trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS idle_sec,
trx_query
FROM INFORMATION_SCHEMA.INNODB_TRX
WHERE trx_state = 'RUNNING'
AND (trx_query IS NULL OR trx_query = '')
AND TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 300;
-
trx_query IS NULL表示事务空闲但未提交,常见于应用连接池配置不当或超时未处理 - 若
TRX_ROWS_LOCKED > 0且trx_state = 'RUNNING',极大概率是应用端忘了COMMIT或异常断连残留 - 别漏掉
XA PREPARED状态:必须单独执行XA RECOVER,有输出就得手动XA COMMIT或XA ROLLBACK,否则对应Undo段永远不可截断
确认Undo表空间是否支持在线截断
innodb_undo_log_truncate = ON只是开关,不是万能钥匙。MySQL 5.7要求innodb_undo_tablespaces ≥ 2(官方推荐≥3),否则Undo全挤在ibdata1里,根本没法轮换、标记、截断。
先验证当前配置:
SHOW VARIABLES LIKE 'innodb_undo%';
- 若
innodb_undo_tablespaces = 0或1,说明配置无效,innodb_undo_log_truncate再开也白搭 - 若已设为≥2,再查
SHOW STATUS LIKE 'Innodb_undo_log_truncated';——返回值长期为0,说明Purge没跑起来,或长事务仍在阻塞 - 注意:
innodb_undo_tablespaces只能在初始化实例时设置,运行中修改不生效,必须停库改配置+重启
加速Purge并强制触发Undo截断
即使Kill完长事务,Purge线程默认每128次调用才尝试释放一次回滚段,响应太慢。临时调高清理频率可加快收缩:
SET GLOBAL innodb_purge_rseg_truncate_frequency = 32;
- 该值越小,Purge释放回滚段越频繁;但别设成1,会增加CPU抖动
- 配合增大单次清理力度:
SET GLOBAL innodb_purge_batch_size = 10000; - 对每个独立Undo表空间主动截断:
ALTER UNDO TABLESPACE undo_001 TRUNCATE;(需逐个执行) - 执行后观察error log是否有
[Note] InnoDB: Truncate undo tablespace 1 completed.,这是真正完成的标志
为什么Kill完事务Undo还不缩?
很多人Kill完事务就去du -sh undo*,发现文件大小纹丝不动——这不是Bug,是设计使然。Undo文件不会“实时缩容”,而是分三步走:
- Purge线程先清空回滚段里的旧版本记录(这步依赖事务已终结)
- 被标记为
inactive的Undo表空间等待下一次重启后才真正删除物理文件(第一次重启建新undo,第二次重启删旧undo) - 即便文件被截断,Linux下
du看到的仍是原大小,除非文件被彻底unlink——所以最终得靠重启+Purge完成后的自动清理
最易忽略的一点:所有操作都依赖innodb_file_per_table = ON已全局生效,且老表已完成ALTER TABLE ENGINE=InnoDB迁移;否则ibdata1仍会继续膨胀,跟Undo表空间无关。


















