必须先结束最老活跃事务,否则innodb_undo_log_truncate无效;真正阻塞purge的是trx_state='RUNNING'且trx_query IS NULL的长事务,需通过INNODB_TRX定位并安全KILL。

开了 innodb_undo_log_truncate 但磁盘还在涨?不是参数没生效,是长事务还在源源不断地生成新 undo,旧的 purge 不掉——所有调参都得先让最老活跃事务结束。
查哪个事务在钉死 undo:只信 INNODB_TRX,别看 PROCESSLIST.TIME
PROCESSLIST.TIME 只记当前语句执行时长,而真正卡住 purge 的,是那个 trx_state = 'RUNNING' 且 trx_query IS NULL 的事务。它可能早执行完了,就卡在没提交。
- 用这条语句抓真凶:
SELECT trx_id, trx_started, TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec, trx_state, trx_rows_modified, trx_query FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 600; - 重点关注
duration_sec > 600且trx_rows_modified > 0的写事务;trx_rows_modified = 0但duration_sec很大,说明是只读事务长期持快照,也要管 -
trx_mysql_thread_id和information_schema.PROCESSLIST.ID是一一对应的,立刻 JOIN 查来源 IP、用户、DB
杀还是不杀:看 trx_state 和 HISTORY LIST LENGTH 再动手
KILL 前必须确认状态,否则可能让回滚更慢、IO 更爆。
-
trx_state = 'RUNNING'且trx_query IS NULL:基本可安全 KILL,大概率是应用漏了COMMIT或连接池未 close -
trx_state = 'LOCK WAIT':先查INNODB_LOCK_WAITS找 blocking 事务,干上游 -
trx_state = 'ROLLING BACK':别动,等它自己完成;此时 KILL 只会让回滚时间翻倍 - 如果
SHOW ENGINE INNODB STATUS\G里History list length> 10000,说明 purge 已严重滞后,KILL 要加SLEEP(0.2)间隔执行,避免雪崩
配置要联动:单独开 innodb_undo_log_truncate 没用
这个参数只是个“裁剪开关”,不是“清理命令”。它只对独立 undo 表空间(undo_001 等)生效,且必须同时满足:长事务已结束、History list length 、后台线程每 128 秒检查一次才可能触发。
- 先确保启用独立 undo:
innodb_undo_tablespaces >= 4(MySQL 5.7+),否则innodb_undo_log_truncate直接跳过 - 配合加大 purge 能力:
SET GLOBAL innodb_purge_batch_size = 10000;,SET GLOBAL innodb_purge_rseg_truncate_frequency = 16; - 别碰
ibdata1里的共享 undo:截断无效,只能重建实例 -
innodb_max_purge_lag = 500000+innodb_max_purge_lag_delay = 100000是合理起点,但它只是节流阀,不是清洁工
根治靠应用:事务生命周期不能交给数据库兜底
所有数据库层配置都是补救措施。undo 膨胀的源头永远在应用侧。
- 禁止在事务里做 HTTP 请求、文件读写、
SLEEP()、复杂计算等耗时操作 - 批量 DML 必须分批:每次最多 1000–5000 行,每批后
COMMIT;用ROW_COUNT()或应用层计数防漏 - ORM(如 MyBatis、Django)显式设
autocommit=true,避免隐式事务跨请求 - 连接池(如 HikariCP)要配置
connection-timeout和idle-timeout,并开启leak-detection-threshold
真正难处理的,从来不是怎么截断 undo 文件,而是那个没人认领、trx_query 为空、trx_started 是三小时前的事务——它卡在那里,purge 就一步也动不了。


















