长事务不提交导致Undo Log无法purge,根本原因是MVCC需保留旧版本供快照读,事务未结束则锁不释放、purge卡住、HISTORY LIST LENGTH持续上涨,进而引发Undo文件膨胀和性能恶化。

长事务不提交,Undo Log 就不会被 purge —— 这不是“占用多”,而是“根本不清”
为什么锁持有时间长直接等于Undo持续膨胀
MySQL 的 MVCC 依赖 Undo Log 提供历史版本快照。只要一个事务还处于 RUNNING 状态(哪怕它早已执行完 SQL、只是没 COMMIT 或 ROLLBACK),InnoDB 就必须保留它修改过的所有行的旧版本,供其他事务做一致性读。
这些旧版本全堆在 Undo 表空间里,而 purge 线程只能清理已提交事务的 Undo 记录。所以:锁没释放 → 事务没结束 → purge 卡住 → HISTORY LIST LENGTH 持续上涨 → Undo 文件越涨越大。
-
HISTORY LIST LENGTH超过 10000 就该警觉;上 10 万基本说明 purge 已严重滞后 - 只读事务(
TRX_ROWS_MODIFIED = 0)也会拖慢 purge,但真正吃空间的是写多且不提交的事务 - 隔离级别为
REPEATABLE READ时,gap lock 范围更大,长事务下锁住的索引范围更广,间接导致更多行的旧版本被强制保留
常见误判:Processlist 的 Time 字段不可信
SHOW PROCESSLIST 里的 Time 只表示当前语句执行时长,和事务生命周期无关。真正钉住 Undo 的,是那些 trx_state = 'RUNNING' 且 trx_query IS NULL 的空闲连接 —— 它们早就跑完了 SQL,却卡在应用层没发 COMMIT。
- 查真实元凶用:
SELECT trx_id, trx_started, trx_state, trx_rows_modified, trx_mysql_thread_id FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 5 - 重点盯
trx_started超过 10 分钟、trx_query为空、trx_rows_modified极大或为 0 的记录 - 用
trx_mysql_thread_id关联information_schema.PROCESSLIST查HOST和USER,确认是不是 Python/Java 应用异常退出后连接未关闭
KILL 线程前必须确认的三件事
盲目 KILL 不仅可能让空间更紧张,还可能引发数据不一致或回滚风暴。
- 看
trx_state:如果是'ROLLING BACK',别动——此时 KILL 会让回滚变慢、IO 更高,反而拖垮 purge - 看
trx_rows_modified:若 > 100000,回滚本身可能耗数分钟,期间仍占 Undo 空间、阻塞 purge 推进 - 看归属:用
PROCESSLIST.HOST和USER定位到具体服务,有些“长事务”是业务必需(如报表导出前预锁),贸然杀掉会导致下游核对失败
物理文件不收缩是正常现象,关键在阻断持续增长
即使 kill 掉所有长事务,undo_001.ibu 或 ibdata1 也不会自动变小 —— InnoDB 不支持 online shrink undo 表空间。
- 启用独立 undo 表空间(
innodb_undo_tablespaces > 0)后,可设innodb_undo_log_truncate = ON,再执行ALTER UNDO TABLESPACE undo_001 TRUNCATE - 若还在用共享表空间(
innodb_undo_tablespaces = 0),唯一办法是mysqldump全库重建,停机时间长、风险高 - 真正要盯住的指标是
HISTORY LIST LENGTH:降到 ≤ 100 才算稳定,而不是盯着磁盘使用率是否回落


















