大事务导致undo log膨胀是因为MVCC要求保留旧版本数据,活跃事务未提交时purge线程无法清理对应undo,加之innodb_max_purge_lag配置不当会加剧空间耗尽。

为什么大事务会让 undo log 膨胀到占满磁盘
因为 InnoDB 的 MVCC 依赖 undo log 保留旧版本数据,只要还有活跃事务(哪怕只是没提交的 SELECT)在读取某一行的旧快照,对应 undo log 就不能被 purge 线程清理。一个运行 2 小时的 UPDATE 大事务,可能产生 GB 级 undo,而 purge 滞后(innodb_max_purge_lag 设置不合理或 purge 线程跟不上)会直接卡住整个实例的 DML。
调整 innodb_max_purge_lag 不是万能解药,得看场景
这个参数控制 purge 延迟阈值(单位:undo log 记录数),当未 purge 的 undo 记录数超过该值,InnoDB 会主动 throttle 新 DML,通过插入 usleep 来减缓写入速度——本质是用写性能换空间安全。但它不解决根本问题:
- 设得太小(如
10000):轻微长事务就触发限流,业务明显变慢 - 设得太大(如
0或10000000):等于关闭保护,undo 持续膨胀直到磁盘爆满 - 它只影响新写入,对已存在的长事务和堆积的 undo 无清理能力
推荐初始值:innodb_max_purge_lag = 500000(约 50 万条 undo 记录),配合 innodb_max_purge_lag_delay = 100000(微秒级延迟),再观察 SHOW ENGINE INNODB STATUS 中的 PURGE DONE 进度。
必须监控长事务,而不是等报警
MySQL 自身不提供“事务运行超 X 分钟”告警,得靠查询 information_schema.INNODB_TRX 主动捞:
SELECT trx_id, trx_state, trx_started,
TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec,
trx_mysql_thread_id, trx_query
FROM information_schema.INNODB_TRX
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 600;
重点关注:
-
trx_state = 'RUNNING'但trx_query IS NULL:极可能是空闲连接未提交(应用没关 transaction) -
trx_query是UPDATE/DELETE且duration_sec > 300:立刻联系业务方确认是否可中断 - 搭配
performance_schema.threads查PROCESSLIST级信息,定位线程来源 IP 和用户
真正治本:从应用侧切断长事务源头
所有数据库层的调参都是兜底手段。undo 膨胀的根因永远在应用:
- 禁止在事务里做 HTTP 请求、文件读写、sleep 等耗时操作
- 批量更新必须分页,单次事务控制在
1000行以内(配合COMMIT) - ORM 框架(如 MyBatis、Django)要显式配置
autocommit=true,避免隐式事务跨请求 - 使用
SET SESSION innodb_lock_wait_timeout = 5缩短锁等待,防止小事务卡死成大事务
最常被忽略的一点:监控脚本本身如果开了事务又没 commit,跑一次就留一条长事务——这类“运维自产”的长事务,在凌晨低峰期最容易悄悄撑爆 undo 表空间。


















