Undo Log溢出主因是单事务删除行数过多、记录镜像太大,须拆分为小事务分批提交;必须用ORDER BY+LIMIT或BETWEEN主键区间确保顺序与可控性,并每批独立提交、及时ANALYZE TABLE。

Undo Log溢出不是因为删得快,而是因为删得太“长”——单个事务内删除行数过多,每行都要存一份完整镜像,undo空间瞬间被撑爆。
DELETE不加LIMIT为什么等于开一个巨型事务
MySQL默认把整个DELETE FROM t WHERE ...当作一个事务处理。InnoDB对每一行执行删除前,必须在undo log中保存该行的完整前镜像(包括TEXT/BLOB字段)。百万行 = 百万份镜像。一旦超出innodb_max_undo_log_size或填满innodb_undo_tablespaces,就会报Undo log is full或The table 'xxx' is full。
- 即使磁盘空间充足,InnoDB也会主动拒绝提交,防止崩溃后无法恢复
-
DELETE LOW_PRIORITY完全无效:它只让语句排队,锁和undo压力一点没少 - 没有
ORDER BY的LIMIT存在风险:优化器可能跳过某些行,或并发写入时重复/漏删
高并发下分页式DELETE仍会漏数据
用LIMIT 5000 OFFSET 10000这类方式,在高并发场景下极易出问题:新插入的记录可能恰好落在下一批OFFSET范围内,导致重复删;也可能因MVCC读视图未更新而漏删。
- 真正可控的是主键区间分段,例如
WHERE id BETWEEN 10001 AND 15000 - 步长建议控制在1000~5000之间,别超10000,否则单次事务日志仍易溢出
- 务必确保
status和id上有联合索引(如INDEX idx_status_id (status, id)),避免全表扫描
删完不ANALYZE TABLE,后续查询可能更慢
大批量删除后,表的统计信息严重滞后,优化器容易选错执行计划(比如该走索引却走全表扫描),间接加剧IO压力,拖慢purge线程进度。
- 每次分批删除后应立即执行
ANALYZE TABLE t - 不要依赖自动统计(
innodb_stats_auto_recalc=ON)——它触发有延迟,且不保证及时 - 如果业务允许,可配合
OPTIMIZE TABLE回收碎片,但注意它会锁表
最常被忽略的一点是:事务是否真正结束。哪怕你写了COMMIT,如果应用层连接没释放、或框架(如Django @transaction.atomic)隐式开启了长事务却忘了commit,那个最早启动的事务就会一直钉住最老read view,让所有undo都无法purge——History list length持续上涨,undo空间就永远清不完。


















