优化mysql undo log需控制事务生命周期、合理配置参数并加快清理:启用独立undo表空间、开启自动截断、避免长事务、拆分大事务、增加purge线程数及批量大小,并利用临时表减少undo压力。

因为每次UPDATE都必须记录旧值供回滚和MVCC使用,且长事务或行长度变化会显著放大Undo日志体积。
UPDATE语句必然生成TRX_UNDO_UPD_EXIST_REC类型的Undo日志
InnoDB执行UPDATE时,不管是否显式开启事务,都会隐式启动事务(autocommit=1下也如此)。它不会直接覆盖原记录,而是:
- 先将被修改行的完整旧值(包括所有字段、
DB_TRX_ID、DB_ROLL_PTR等)写入Undo Log - 该日志类型固定为
TRX_UNDO_UPD_EXIST_REC,结构含更新列位置、旧长度、旧值等元数据 - 即使只改一个
VARCHAR字段,只要内容变长(如从“abc”扩到“hello world”),就触发“删除旧记录+插入新记录”流程,产生更长的Undo日志
主键更新或行长度变化会让Undo日志翻倍
不更新主键时,若被更新列长度不变(如name从“zhangsan”→“lisi”),走就地更新,Undo日志相对紧凑;但以下情况会显著膨胀:
-
UPDATE涉及主键变更:InnoDB先做DELETE MARK再插入新行,相当于一次DELETE+ 一次INSERT,生成两条Undo日志(TRX_UNDO_DEL_MARK_REC+TRX_UNDO_INSERT_REC) - 任意被更新列长度变化(如
TEXT字段追加内容、JSON字段嵌套加深):触发页内重分配,旧记录被物理移除,新记录另占空间,Undo需完整保留旧页布局信息 - 页面分裂发生时:Undo日志还需额外记录页分裂前后的父子页关系,进一步增加体积
长事务阻塞Undo日志清理,导致历史版本堆积
Undo Log不能随便删——它要等到所有可能用到该版本的事务都结束才能被Purge线程回收。常见卡点:
- 一个运行2小时的
SELECT事务未提交,它启动时刻的read view仍认为某些旧版本可见,对应Undo链无法清理 -
information_schema.innodb_trx中trx_started时间过早的事务,会把整个Undo历史链“钉住” -
innodb_max_undo_log_size设得过大(如默认1GB),加上innodb_undo_log_truncate=OFF,Undo文件只增不缩 - MySQL 5.7中
innodb_undo_tablespaces未启用,Undo挤在系统表空间ibdata1里,膨胀后无法在线收缩
真正麻烦的不是“一次Update写多少Undo”,而是“这些Undo被谁锁着、多久能释放”。业务里一个未提交的调试连接、一段带SLEEP()的测试SQL、或者应用层异常中断后残留的连接,都可能让百万级Update产生的Undo持续占用磁盘数天。监控history_list_length指标比看慢查询更重要——它直接反映待清理Undo版本数量。











