Undo Log膨胀必然发生,根本原因是MVCC机制下长事务(尤其是trx_state='RUNNING'且trx_query IS NULL)钉住旧版本快照,阻塞purge线程清理;调参、截断均无效,必须先定位并终止最老空闲事务,再手动推进purge。

Undo Log 膨胀不是“容易发生”,而是**必然发生**——只要存在长时间未提交的事务,innodb_undo_tablespaces 就会持续增长,直到磁盘写满。根本原因不在配置,而在 MVCC 的实现机制本身。
为什么大事务直接卡死 purge 线程
InnoDB 用 Undo Log 保存行的历史版本,供一致性读(REPEATABLE READ)使用。只要有一个活跃事务(哪怕只是空闲连接)仍持有旧的 read view,它访问过的所有旧版本数据就都不能被清理。
-
trx_state = 'RUNNING'且trx_query IS NULL的事务最危险:它不执行任何语句,却一直钉住整个快照链 - 一个运行 15 分钟的
UPDATE事务可能生成数百万条 undo 记录,而 purge 线程只能在它提交后才开始清理 -
SHOW ENGINE INNODB STATUS\G中的History list length值就是当前堆积的未 purge undo 数量,> 50000 就已高危
为什么调 innodb_max_purge_lag 没用
这个参数只控制“新写入是否要减速”,对已堆积的 undo 完全无感:
- 设为
0:等于关闭保护,undo 继续疯长,直到ERROR 1030 (HY000): Got error 28 from storage engine - 设为
10000:轻微长事务就触发限流,业务 DML 明显变慢,但 history list 依然卡住不动 - 它不终止事务、不推进 purge、不释放空间——只是让写入更慢一点
为什么 ALTER UNDO TABLESPACE ... TRUNCATE 会失败
MySQL 8.0 要求 truncate 前必须满足两个硬条件,缺一不可:
- 至少有
2个 active 的 undo 表空间(默认只有innodb_undo_tablespaces = 2,不够轮换) -
History list length必须显著回落(purge 已跟上),否则报错:ERROR 3655 (HY000): Cannot set innodb_undo_001 inactive since there would be less than 2 undo tablespaces left active - 手动删
.ibu文件或改ibdata1里的 undo 段:实例启动失败,必须从备份恢复
真正卡住空间回收的,永远是那个最老的空闲事务
别看 SHOW PROCESSLIST 的 Time 字段——它只记当前语句执行时长。真正钉死 undo 的,是 INNODB_TRX 里 trx_started 最早、trx_query IS NULL 的那条记录:
- 用这条语句定位:
SELECT trx_id, trx_started, TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec, trx_state, trx_query FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 1 - 查到后立刻关联
PROCESSLIST:SELECT USER, HOST, COMMAND, INFO FROM information_schema.PROCESSLIST WHERE ID = ? - 如果
INFO为空、COMMAND = 'Sleep'、HOST来自应用服务器 IP——基本可判定是 ORM 未 close 或 HTTP 调用后漏了COMMIT
History list length 不会立刻下降。你得盯住 PURGE DONE for trx's n:o 是否在推进,再临时调大 innodb_purge_batch_size,最后等 purge 跟上才能安全 TRUNCATE。每一步跳过,都会让空间继续卡死。


















