长事务导致History list length飙升,因其使未提交事务修改的每行旧版本undo记录均被钉住不释放;trx_state='RUNNING'且trx_query IS NULL的空闲事务最危险,它不执行SQL却长期霸占ReadView,阻塞purge线程清理,尤其在REPEATABLE READ隔离级别下影响更大。

长事务为什么让History list length飙升
History list length不是磁盘占用值,而是“等待被purge的undo记录总数”。只要事务状态是trx_state = 'RUNNING',它修改过的每一行旧版本都得留着——哪怕事务早已执行完SQL,只是没发COMMIT或ROLLBACK。一个UPDATE改了5万行,就生成至少5万条undo记录,全被钉住不放。
特别危险的是trx_query IS NULL的空闲事务:它不干活、不报错、不超时,却长期霸占ReadView,让所有后续事务的快照读都依赖这些旧版本。REPEATABLE READ隔离级别下,这种影响比READ COMMITTED高得多。
-
TRX_ROWS_MODIFIED > 0且持续超过300秒的事务,是undo膨胀主力 - 只读事务(
TRX_ROWS_MODIFIED = 0)也会拖慢purge,但不新增undo,空间压力小得多 -
SHOW PROCESSLIST里的Time字段不可信——它只算当前语句执行时长,和事务生命周期无关
MySQL 5.7默认配置如何放大问题
5.7默认用共享undo表空间ibdata1,且innodb_undo_log_truncate在该模式下完全无效。你设成ON也没用,因为逻辑直接跳过——根本没法对ibdata1做在线截断。
同时innodb_purge_batch_size默认仅300,面对几万条undo记录,purge线程清理效率极低;innodb_max_purge_lag若为0或过大,purge线程会彻底躺平。
-
innodb_undo_tablespaces = 0(5.7默认)→ undo挤进ibdata1,文件只增不减 - 独立undo表空间未启用 →
innodb_undo_log_truncate形同虚设 - 迁移或大更新期间,History list length动辄上百万,
ibdata1涨到几十GB很常见
KILL之后undo文件为啥还不缩
杀掉事务不等于立刻释放空间。回滚本身要写undo、刷磁盘、更新页,可能持续数分钟;之后purge线程才开始清理残留记录。而5.7的ibdata1一旦膨胀,就无法收缩——你看到的文件大小不会回落,只是内部空间被标记为可复用。
别盯着du -sh ibdata1,要看SHOW ENGINE INNODB STATUS\G里PURGE DONE是否推进,以及History list length是否从几万降到几百。这个过程通常需要数十秒到数分钟,取决于IO压力和innodb_purge_batch_size设置。
- 盲目KILL正在
ROLLING BACK的事务,会让回滚变慢、IO更高,反而拖垮purge -
trx_rows_modified > 100000的大事务,回滚本身可能耗数分钟,期间仍占undo空间 - 物理文件不收缩是正常现象,关键在阻断持续增长——先停源头,再调参数
怎么快速定位真凶事务
别靠SHOW PROCESSLIST猜,直接查INFORMATION_SCHEMA.INNODB_TRX:
SELECT trx_id, trx_state, trx_started,
TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec,
trx_rows_modified,
SUBSTRING(trx_query, 1, 80) AS trx_query_truncated
FROM information_schema.INNODB_TRX
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 600;
重点关注三类:
-
trx_state = 'RUNNING'且trx_query IS NULL:99%是连接池泄漏或ORM事务未关闭 -
trx_rows_modified > 10000且duration_sec > 300:比如批量UPDATE没分页,卡住了 -
trx_state = 'LOCK WAIT':得先查INNODB_LOCK_WAITS找blocking线程
trx_mysql_thread_id可JOIN performance_schema.threads查客户端IP和用户,避免误杀业务必需的长事务(如报表导出前预锁)。


















