history list length持续上涨是因为长事务阻塞purge线程,使其无法清理被引用的undo记录;此时调大innodb_purge_threads或innodb_purge_batch_size无效,必须先终止活跃长事务,再结合索引优化分批delete、合理配置purge参数及分区表策略综合治理。

为什么History List Length持续上涨,Purge线程却没反应
不是Purge线程“不干活”,而是它被卡住了——只要还有活跃事务在读老版本(比如一个跑了90分钟的报表查询),对应undo记录就无法清理。此时调大innodb_purge_threads或innodb_purge_batch_size毫无作用。
先确认是不是真瓶颈:SHOW ENGINE INNODB STATUS里看HISTORY LIST LENGTH是否稳定 > 10000;再查长事务:SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60。如果返回结果里有未提交事务,优先杀掉或让应用修复连接泄漏。
innodb_purge_threads和innodb_purge_batch_size怎么配才有效
盲目设高反而更慢:设成8个Purge线程会争抢purge_sys->mutex,挤占buffer pool刷脏页资源;单次batch太大(如设到5000)又会让Purge线程卡住,阻塞MVCC快照构建,引发Lock wait timeout exceeded错误。
- 纯读/低频更新库:
innodb_purge_threads = 1足够 - 高频DELETE/UPDATE场景(如日志表定时清理):
innodb_purge_threads = 2~4,别超4 - 必须同步调
innodb_purge_batch_size:从默认300提到1000~3000,但别超5000 -
SET GLOBAL innodb_purge_threads = 4不会立刻生效,只影响下一轮Purge循环启动的新线程
分批DELETE时WHERE条件没走索引,Purge照样扛不住
加了LIMIT但没索引,每次删都全表扫描,不仅慢,还持续生成undo、推高History List Length。更糟的是,没索引的分批逻辑(比如WHERE status = 0 LIMIT 1000)根本不可靠——InnoDB不保证无序扫描顺序,可能反复删同一组行,漏删其他行。
- WHERE条件必须命中复合索引最左前缀,推荐
(status, id)这类组合 - 每次必须带
ORDER BY id LIMIT 1000,否则优化器可能改变扫描路径 - 下一批起点要锚定:上一批最大
id是12345,下一批写WHERE status = 0 AND id > 12345 ORDER BY id LIMIT 1000
删完数据磁盘空间还不释放,别急着OPTIMIZE TABLE
DELETE只是标记删除,真正释放空间靠Purge线程清理undo + 回收页;而即使Purge完成,.ibd文件大小也不会自动缩小——InnoDB默认不把空页还给文件系统。
想真正回收磁盘空间,只有两个办法:
-
OPTIMIZE TABLE:重建表,锁表时间长,且仅在innodb_file_per_table = ON时有效 -
TRUNCATE TABLE或DROP + CREATE:立刻释放,但不可回滚、不记binlog
日常更稳妥的做法是建按时间分区的表,清理时直接DROP PARTITION,物理删除、零延迟、无undo压力。











