文章目录
-
- 前言
- 【生产环境重要提示】先读这一节
-
- [1. 参数生效方式速查](#1. 参数生效方式速查)
- [2. 变更前必须确认的事项](#2. 变更前必须确认的事项)
- [3. 最容易踩的三个坑](#3. 最容易踩的三个坑)
- [一、purge 到底在做什么](#一、purge 到底在做什么)
-
- [1.1 undo log 的两种类型](#1.1 undo log 的两种类型)
- [1.2 MVCC 与版本链](#1.2 MVCC 与版本链)
- [1.3 purge 的判定条件](#1.3 purge 的判定条件)
- [1.4 History list](#1.4 History list)
- [二、purge 的线程架构](#二、purge 的线程架构)
-
- [2.1 coordinator + worker](#2.1 coordinator + worker)
- [2.2 一次 purge 的流程(简化)](#2.2 一次 purge 的流程(简化))
- [2.3 并行度的真实限制](#2.3 并行度的真实限制)
- 三、innodb_purge_threads
-
- [3.1 参数说明](#3.1 参数说明)
- [3.2 这个参数控制什么](#3.2 这个参数控制什么)
- [3.3 版本差异](#3.3 版本差异)
- [3.4 调大能带来什么,代价是什么](#3.4 调大能带来什么,代价是什么)
- [3.5 修改方式(重点:需要重启)](#3.5 修改方式(重点:需要重启))
- [3.6 生产变更注意事项](#3.6 生产变更注意事项)
- 四、innodb_purge_batch_size
-
- [4.1 参数说明](#4.1 参数说明)
- [4.2 单位是 undo page,不是行](#4.2 单位是 undo page,不是行)
- [4.3 多线程下 batch 是"总预算",不是"每线程预算"](#4.3 多线程下 batch 是"总预算",不是"每线程预算")
- [4.4 调大 / 调小的取舍](#4.4 调大 / 调小的取舍)
- [4.5 修改方式(动态,立即生效)](#4.5 修改方式(动态,立即生效))
- [4.6 生产变更注意事项](#4.6 生产变更注意事项)
- 五、配套参数
-
- [5.1 innodb_max_purge_lag【高风险】](#5.1 innodb_max_purge_lag【高风险】)
- [5.2 innodb_max_purge_lag_delay](#5.2 innodb_max_purge_lag_delay)
- [5.3 innodb_purge_rseg_truncate_frequency](#5.3 innodb_purge_rseg_truncate_frequency)
- [5.4 innodb_undo_log_truncate【高风险】](#5.4 innodb_undo_log_truncate【高风险】)
- [5.5 innodb_undo_tablespaces【已废弃,不要改】](#5.5 innodb_undo_tablespaces【已废弃,不要改】)
- [5.6 innodb_rollback_segments](#5.6 innodb_rollback_segments)
- [5.7 innodb_max_undo_log_size](#5.7 innodb_max_undo_log_size)
- 六、监控与诊断
-
- [6.1 查看 History list length](#6.1 查看 History list length)
- [6.2 通过 INNODB_METRICS 查看 purge 指标](#6.2 通过 INNODB_METRICS 查看 purge 指标)
- [6.3 找出阻塞 purge 的长事务](#6.3 找出阻塞 purge 的长事务)
- [6.4 查看 purge 相关线程和参数](#6.4 查看 purge 相关线程和参数)
- [6.5 查看 undo 表空间文件](#6.5 查看 undo 表空间文件)
- 七、典型场景与调优案例
-
- [场景一:批量 DELETE 后 undo 表空间暴涨](#场景一:批量 DELETE 后 undo 表空间暴涨)
- [场景二:History list 不降,线程数调大也没用](#场景二:History list 不降,线程数调大也没用)
- [场景三:单表热点导致 purge 跟不上](#场景三:单表热点导致 purge 跟不上)
- [场景四:开启 undo 截断后业务出现卡顿](#场景四:开启 undo 截断后业务出现卡顿)
- 八、参数速查表
- 九、生产变更检查清单
- 十、总结
- 参考资料
前言
在 MySQL 的日常运维中,只要出现"undo 表空间持续膨胀""History list length 居高不下""磁盘空间只增不减"这几类问题,排查到最后基本都会落到 InnoDB 的 purge(清理) 机制上。而控制 purge 行为的核心参数,就是 innodb_purge_threads 和 innodb_purge_batch_size。
很多资料只给出"调大线程数""调大 batch"的结论,却没说清楚这两个参数分别控制什么、单位是什么、调大之后的代价是什么,更没有说明哪些参数改完必须重启、哪些改动会直接影响生产业务。本文从 undo log 和 MVCC 的原理讲起,把 purge 的工作流程拆开,逐个分析这两个参数以及配套参数,并单独列出生产环境的高风险操作。
本文以 MySQL 5.7 / 8.0 为主,涉及 5.6、8.4 的差异会单独标注。
【生产环境重要提示】先读这一节
1. 参数生效方式速查
| 参数 | 动态性 | 生效方式 | 主要风险 |
|---|---|---|---|
innodb_purge_threads |
只读(Dynamic: No) | 修改配置文件后重启 ;8.0 可用 SET PERSIST_ONLY 持久化,重启后生效 |
必须重启 ,存在业务中断风险;SET GLOBAL 会直接报错 |
innodb_purge_batch_size |
动态(Dynamic: Yes) | SET GLOBAL 立即生效 |
调大可能导致 CPU / I/O 短时抖动 |
innodb_max_purge_lag |
动态 | SET GLOBAL 立即生效 |
会主动延迟 INSERT / UPDATE / DELETE,可能造成业务超时、TPS 下跌 |
innodb_max_purge_lag_delay |
动态 | SET GLOBAL 立即生效 |
只限制延迟上限,不解决 purge 慢的根因 |
innodb_purge_rseg_truncate_frequency |
动态 | SET GLOBAL 立即生效 |
调小会增加 undo 表空间 truncate 频率和 I/O |
innodb_undo_log_truncate |
动态 | SET GLOBAL 立即生效 |
8.0 默认开启;官方明确提示自动截断有持续性能开销,可能造成卡顿 |
innodb_rollback_segments |
动态 | SET GLOBAL 立即生效 |
影响 undo 资源分配,不宜随意调整 |
innodb_undo_tablespaces |
已废弃、不可配置 | 无法修改 | 设置不生效,不要试图用它调优 |
2. 变更前必须确认的事项
- 确认 MySQL 版本:5.7、8.0、8.4 的默认值和动态性不同,不能照搬网上的参数值。
- 先在测试实例验证:尤其是只读参数,改完必须重启才能验证效果。
- 准备回滚方案:保留原配置文件,确认主从切换、连接重连、业务重试机制可用。
- 选择低峰期操作:重启和批量 purge 都会带来资源波动。
- 明确根因:如果是长事务阻塞 purge,调参数无效,必须先处理长事务。
- 持续观察:History list length、CPU、I/O、复制延迟、业务 P99 延迟,缺一不可。
3. 最容易踩的三个坑
-
坑一:以为
innodb_purge_threads可以动态改。执行
SET GLOBAL innodb_purge_threads = 8;会报:ERROR 1238 (HY000): Variable 'innodb_purge_threads' is a read only variable必须改配置文件并重启。
-
坑二:把
innodb_purge_batch_size当成"每个线程处理多少行"。它的单位是 undo page,不是行;而且多线程下是"总预算按线程数拆分",不是每个线程都拿完整的 batch。
-
坑三:为了压 History list 而打开
innodb_max_purge_lag。这个参数的本质是主动延迟 DML,如果 purge 是被长事务卡住的,开启它只会让业务更慢。
一、purge 到底在做什么
1.1 undo log 的两种类型
InnoDB 的 undo log 分为两类:
- insert undo :
INSERT语句产生的 undo,只在事务回滚时需要。事务提交后,对应的 undo 空间可以直接回收,不需要 purge。 - update undo :
UPDATE、DELETE语句产生的 undo,除了回滚之外,还要服务于 MVCC 的快照读。事务提交后不能立即删除,必须等没有任何活跃读视图(read view)再需要它时,才能由 purge 清理。
所以,History list 的长度主要由 UPDATE / DELETE 产生。这也解释了一个常见现象:批量插入几百万行后 undo 能很快回收,而批量删除几百万行后 undo 会长时间不释放。
1.2 MVCC 与版本链
InnoDB 通过 undo log 构建行的历史版本,形成版本链:
当前行记录 → undo 版本 v3 → undo 版本 v2 → undo 版本 v1
当一个事务执行 UPDATE 时:
- 把当前行的旧值写入 undo log;
- 将当前行标记为 delete-mark(逻辑删除);
- 插入一条新版本的行记录。
DELETE 则是把行标记为 delete-mark,并不立即物理删除。
purge 要做的,就是物理删除已经被 delete-mark 且不再被任何读视图需要的记录 ,同时回收不再需要的 undo 记录。
1.3 purge 的判定条件
一条 undo 记录能否被清理,取决于两个条件同时满足:
- 产生该 undo 的事务已经提交(或回滚);
- 当前没有任何活跃的 read view 还需要这个版本。
第 2 点是关键。只要有长事务、长查询、未提交事务,甚至是一个开了事务却一直不提交的会话,purge 就无法推进到该事务开始时间点之后的版本。
官方文档对此有明确说明:在 REPEATABLE READ 等一致性读隔离级别下,事务必须返回与创建 read view 时一致的结果,因此 InnoDB 必须保留 undo 中的旧数据,直到所有依赖它的事务结束。典型例子包括:
- 使用
--single-transaction的mysqldump,同时存在大量并发 DML; - 关闭
autocommit后执行了SELECT,却忘记COMMIT或ROLLBACK。
结论:purge 的推进速度不取决于 undo 有多少,而取决于最老的那个活跃 read view 有多老。
1.4 History list
InnoDB 把等待 purge 的 undo 记录挂在各个 rollback segment 的 history list 上,所有 rollback segment 的 history list 长度之和,就是监控中常见的 History list length ,也就是 purge lag。
它有三个特点:
- 正常值通常很低,官方描述为"一般小于几千";
- 长期单调增长通常意味着 purge 跟不上,或存在长事务;
- 它是 undo 表空间膨胀的直接来源;
- 它会在
SHOW ENGINE INNODB STATUS的TRANSACTIONS段中显示。
sql
SHOW ENGINE INNODB STATUS\G
-- 在输出中查找:
-- Purge done for trx's n:o < 0 290315608 undo n:o < 0 17
-- History list length 15234
其中 Purge done for trx's n:o ... 表示 purge 已经推进到的位置,可以配合 History list length 判断 purge 是否在前进。
二、purge 的线程架构
2.1 coordinator + worker
从 MySQL 5.6 开始,InnoDB 支持多个 purge 线程,并形成了 purge coordinator(协调线程)+ purge worker(工作线程) 的架构:
┌─────────────────────┐
│ purge coordinator │ 分配任务、维护 history list
└──────────┬──────────┘
│ 分发 undo page
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ worker1 │ │ worker2 │ │ worker3 │
└─────────┘ └─────────┘ └─────────┘
innodb_purge_threads = 1:没有独立 worker,协调线程自己执行 purge;innodb_purge_threads > 1:协调线程负责分发,具体清理由多个 worker 并行完成。
需要特别注意:innodb_purge_threads 设置的是允许使用的最大线程数 ,purge 系统会根据实际负载自动调整真正使用的线程数。它不是一个"固定开这么多线程"的参数。
2.2 一次 purge 的流程(简化)
- 协调线程从 history list 中取出待处理的 undo page;
- 按照最老的活跃 read view 判断哪些 undo 记录可以清理;
- 把可清理的 undo page 分发给 worker 线程;
- worker 线程物理删除 delete-mark 记录、清理索引项、回收 undo page;
- 处理完一批(batch)后,协调线程继续下一轮。
这个"一批"的大小,就是由 innodb_purge_batch_size 控制的。
2.3 并行度的真实限制
purge 并不是"线程越多越快"。官方文档明确指出:
- 如果 DML 集中在单张表 上,这张表的 purge 只能由一个 purge 线程处理,可能出现 purge 变慢、purge lag 上升;如果涉及大对象(LOB),表空间文件还会持续增长。
- 如果 DML 集中在少数几张表 上,应把
innodb_purge_threads设得低一些,避免多个 purge 线程互相争抢这些热点表。 - 如果 DML 分散在很多表 上,才考虑调高
innodb_purge_threads。 - 从 MySQL 8.0.26 开始,当
innodb_max_purge_lag被超过时,purge 工作会自动在可用线程之间重新分配。 - 活跃的 purge 线程过多,会与用户线程产生竞争。
所以,"单表热点写入导致 purge 跟不上"这个场景,调大线程数基本无效。
三、innodb_purge_threads
3.1 参数说明
| 项目 | 说明 |
|---|---|
| 参数名 | innodb_purge_threads |
| 作用域 | Global |
| 是否动态 | 否(只读参数,Dynamic: No) |
| 类型 | Integer |
| 默认值 | 5.6 为 1;5.7 / 8.0 为 4;8.4 为"逻辑 CPU ≤ 16 时取 1,否则取 4" |
| 取值范围 | 1 ~ 32 |
| 单位 | 线程数(允许的最大 purge 线程数) |
3.2 这个参数控制什么
它控制的是 purge 后台线程的最大数量,也就是 purge 的并行度上限。
- 值为 1 时,没有独立的 worker,协调线程既分发又干活;
- 值越大,能同时处理的 undo page 越多,在多表、多 rollback segment 的高并发写入场景下,purge 吞吐会更好;
- 实际使用多少个线程由 purge 系统自动决定,不是固定值。
3.3 版本差异
| 版本 | 默认值 | 说明 |
|---|---|---|
| MySQL 5.6 | 1 | 开始支持多个 purge 线程 |
| MySQL 5.7 | 4 | 默认提升到 4 |
| MySQL 8.0 | 4 | 与 5.7 相同 |
| MySQL 8.4 | 逻辑 CPU ≤ 16 时为 1,否则为 4 | 默认值改为按 CPU 数量计算 |
3.4 调大能带来什么,代价是什么
收益:
- 多表并发写入场景下,History list 下降更快;
- 大批量 DML 之后,undo 表空间增长更可控;
- 减少因 purge 滞后导致的 DML 延迟。
代价与风险:
- 必须重启才能生效,这是最大的生产风险;
- 更多后台线程争抢 CPU,高并发下可能与前台业务争抢资源;
- 单表热点场景下,多线程无法并行处理同一张表,收益有限;
- 线程过多会增加协调和竞争开销。
经验建议:
- 默认值 4 对大多数场景已经够用,不要盲目调大;
- 如果是单表热点导致 purge 慢,优先考虑拆分热点、优化批量 DML,而不是加线程;
- 如果确实是多表分散写入且 purge 长期落后,可以评估调到 8,但必须安排重启窗口;
- 如果是长事务阻塞导致的 purge 停滞,调线程数没有任何作用。
3.5 修改方式(重点:需要重启)
错误做法(会直接报错):
sql
-- 只读参数,运行期不能修改
SET GLOBAL innodb_purge_threads = 8;
-- ERROR 1238 (HY000): Variable 'innodb_purge_threads' is a read only variable
正确做法一:修改配置文件并重启
ini
# my.cnf
[mysqld]
innodb_purge_threads = 8
bash
# 重启 MySQL 服务(生产环境请走变更流程)
systemctl restart mysqld
正确做法二:MySQL 8.0 使用 SET PERSIST_ONLY 持久化,重启后生效
sql
-- 只写入 mysqld-auto.cnf,不改变当前运行值
SET PERSIST_ONLY innodb_purge_threads = 8;
注意:
SET PERSIST_ONLY需要SYSTEM_VARIABLES_ADMIN和PERSIST_RO_VARIABLES_ADMIN权限。它不会立即生效,仍然需要重启。
3.6 生产变更注意事项
- 重启是高风险操作:会中断连接、触发主从切换、影响未提交事务,必须安排维护窗口。
- 重启后要确认参数确实生效:
sql
SHOW VARIABLES LIKE 'innodb_purge_threads';
- 重启后观察 History list length 是否下降,以及 CPU、I/O、业务延迟是否正常。
- 如果实例是主库,重启前确认从库延迟已追平,避免切换后数据不一致。
四、innodb_purge_batch_size
4.1 参数说明
| 项目 | 说明 |
|---|---|
| 参数名 | innodb_purge_batch_size |
| 作用域 | Global |
| 是否动态 | 是(Dynamic: Yes) |
| 类型 | Integer |
| 默认值 | 300 |
| 取值范围 | 1 ~ 5000 |
| 单位 | undo page 数(不是行数) |
官方说明:该参数是高级性能调优参数,大多数用户不需要修改默认值。
4.2 单位是 undo page,不是行
这是最容易理解错的地方。官方定义是:purge 从 history list 中一次解析和处理的 undo log page 数量。
一个 undo page 默认 16KB,里面通常包含几十到上百条 undo 记录。也就是说:
一次 batch 涉及的 undo 记录数 ≈ batch_size × 每个 page 的 undo 记录数
所以 innodb_purge_batch_size = 300 并不意味着"一次清理 300 行",实际涉及的记录数往往远大于 300。
此外,该参数还定义了 purge 每 128 次迭代释放的 undo log page 数量,这个 128 是内部固定节奏。
4.3 多线程下 batch 是"总预算",不是"每线程预算"
这一点非常关键,很多资料写错了。
官方原文:在多线程 purge 配置下,协调线程会把 innodb_purge_batch_size 除以 innodb_purge_threads,再把结果分配给每个 purge 线程。
也就是说:
每个线程分到的 page 数 ≈ innodb_purge_batch_size / innodb_purge_threads
所以:
- 调大
innodb_purge_threads不会让每轮总处理量成倍增加,它只是把同一份"总预算"拆给更多线程并行处理; - 如果同时把线程数调大、batch 保持不变,每个线程分到的 page 数反而会变少;
- 想要提高每轮总处理量,应该调大
innodb_purge_batch_size,而不是只加线程。
4.4 调大 / 调小的取舍
调大(例如 1000 ~ 2000):
- 单轮 purge 处理更多 undo page,减少线程唤醒和调度次数;
- 适合 History list 很长、需要快速追赶的场景;
- 缺点是单次 purge 占用 CPU 时间更长,可能造成短时抖动,同时放大 I/O 压力。
调小:
- purge 更"细水长流",对前台业务更友好;
- 但 History list 下降更慢,极端情况下可能追不上写入速度。
经验建议:
- 默认 300 在多数场景下是均衡的,官方也建议大多数用户不要改;
- 当 History list 持续增长、且确认 purge 线程并未打满时,可以逐步调大到 1000;
- 不建议一上来就设到 5000,容易造成周期性的性能抖动;
- 调整后要观察 CPU、I/O 以及 History list 的变化,而不是只看单一指标。
4.5 修改方式(动态,立即生效)
sql
-- 立即生效,但重启后失效
SET GLOBAL innodb_purge_batch_size = 1000;
-- 8.0:立即生效并持久化
SET PERSIST innodb_purge_batch_size = 1000;
ini
# 或者写入配置文件
[mysqld]
innodb_purge_batch_size = 1000
4.6 生产变更注意事项
- 该参数立即生效,不需要重启,但这不代表没有风险。
- 调大后可能出现 CPU 使用率上升、磁盘 I/O 抖动、复制延迟增加。
- 建议在低峰期调整,调整后至少观察 10 ~ 30 分钟。
- 如果业务对延迟敏感,采用"小步调整"(例如 300 → 500 → 1000),不要一次跳太大。
五、配套参数
purge 不是一个孤立的参数,它和下面几个参数共同决定行为。其中 innodb_max_purge_lag 和 innodb_undo_log_truncate 属于高风险参数,生产环境修改前必须充分评估。
5.1 innodb_max_purge_lag【高风险】
| 项目 | 说明 |
|---|---|
| 默认值 | 0(不限制,不延迟) |
| 取值范围 | 0 ~ 4294967295 |
| 动态 | 是 |
| 单位 | History list length 阈值 |
当 History list length 超过该值时,InnoDB 会主动延迟 INSERT、UPDATE、DELETE 操作,给 purge 争取时间。延迟在每次 purge batch 开始时计算。
延迟计算公式:
- MySQL 8.0.14 之前:
(purge_lag / innodb_max_purge_lag - 0.5) * 10000微秒,最小延迟 5000 微秒; - MySQL 8.0.14 及以后:
(purge_lag / innodb_max_purge_lag - 0.9995) * 10000微秒,最小延迟 5 微秒。
官方给出的一个典型示例值是 1000000(100 万),但这是在"事务很小、允许约 100MB 未 purge 数据"的前提下的示例,不是通用推荐值。
生产风险(必须重视):
- 这是"牺牲业务响应来保护数据库"的机制,开启后 DML 会变慢;
- 如果 purge 慢是因为长事务阻塞,延迟 DML 并不能让 purge 变快,只会把业务一起拖慢;
- 高并发下可能引发连接池耗尽、请求超时、TPS 断崖式下跌;
- 如果通过
SET PERSIST设置,该值会写入mysqld-auto.cnf,重启后依然生效,务必记录并确认,避免"临时测试"变成永久配置; - 很多团队选择保持默认 0,改用监控告警发现 History list 异常。
建议: 除非明确知道自己在做什么,否则不要在生产环境随意设置非 0 值。先解决长事务和 purge 瓶颈,再考虑是否需要该参数。
5.2 innodb_max_purge_lag_delay
| 项目 | 说明 |
|---|---|
| 默认值 | 0(不限制) |
| 取值范围 | 0 ~ 10000000 |
| 单位 | 微秒 |
| 动态 | 是 |
用于限制 innodb_max_purge_lag 触发的最大延迟时间。只有设置了 innodb_max_purge_lag 时它才有意义。
风险: 它只是给延迟设上限,不能解决 purge 慢的根因;设置过小会让 innodb_max_purge_lag 的保护效果形同虚设。
5.3 innodb_purge_rseg_truncate_frequency
| 项目 | 说明 |
|---|---|
| 默认值 | 128 |
| 取值范围 | 1 ~ 128 |
| 动态 | 是 |
控制 purge 系统释放 rollback segment 的频率:每执行 N 次 purge 才尝试一次 rollback segment 的释放。undo 表空间必须先释放 rollback segment,才能被截断。
- 默认 128,即每 128 次 purge 尝试一次;
- 调小该值会增加尝试频率,从而更快触发 undo 表空间截断;
- 该参数是为
innodb_undo_log_truncate服务的。
风险: 调得太小(例如 1)会让 truncate 检查过于频繁,增加 I/O 和 purge 开销。建议按需从 128 逐步下调到 32 左右观察,不要直接设 1。
5.4 innodb_undo_log_truncate【高风险】
| 项目 | 说明 |
|---|---|
| 默认值 | 8.0 / 8.4 为 ON;5.7 为 OFF |
| 动态 | 是 |
| 前置条件 | 至少 2 个活跃的 undo 表空间 |
开启后,超过 innodb_max_undo_log_size(默认 1073741824 字节,即 1GB)的 undo 表空间会被标记为可截断,最终截断到 16MB。注意:
- 只有独立的 undo 表空间才能被截断,系统表空间中的 undo 不支持截断;
- 至少需要 2 个 undo 表空间,保证一个在线服务、另一个离线截断;
- 截断前需要 purge 清空对应的 rollback segment,因此长事务同样会阻止截断。
生产风险(官方明确提示):
MySQL 8.0 升级文档在"Valid Performance Regressions"一节中明确指出:8.0 默认开启 undo 表空间截断,会带来持续的性能开销,并可能导致卡顿(stalls) 。官方给出的缓解方式是:将 innodb_undo_log_truncate 设为 OFF,需要时再手动截断。
因此:
- 不要以为"开启截断只有好处",它是以一定的性能开销换取空间回收;
- 对延迟极其敏感的核心库,应评估是否保持默认 ON;
- 如果关闭自动截断,需要建立 undo 表空间使用率的监控,必要时手动处理;
- 修改前先在测试环境观察业务延迟和 I/O 变化。
5.5 innodb_undo_tablespaces【已废弃,不要改】
| 项目 | 说明 |
|---|---|
| 默认值 | 2(同时也是最小值) |
| 取值范围 | 2 ~ 127 |
| 状态 | 已废弃;MySQL 8.0.14 起不再可配置,8.4 中设置无效 |
- 默认在初始化时创建 2 个 undo 表空间;
- 自动截断要求至少有 2 个 undo 表空间;
- 8.0.14 之后该参数已废弃,不要再试图通过修改它来增加 undo 表空间 ;8.0 中可以通过
CREATE UNDO TABLESPACE增加 undo 表空间。
风险: 在 8.0.14+ 修改该参数不会生效,容易被误认为"调优成功",实际什么都没变。
5.6 innodb_rollback_segments
| 项目 | 说明 |
|---|---|
| 默认值 | 128 |
| 取值范围 | 1 ~ 128 |
| 动态 | 是 |
定义每个 undo 表空间(以及全局临时表空间)分配的 rollback segment 数量。
- rollback segment 越多,并发事务越容易使用不同的 rollback segment,减少 undo 资源竞争;
- 每个 undo 表空间最多支持 128 个 rollback segment;
- 版本差异:MySQL 8.0 之前,该参数表示整个实例的 rollback segment 总数;8.0 起改为"每个 undo 表空间的数量",可用数量因此大幅增加。
建议: 默认 128 通常无需调整。
5.7 innodb_max_undo_log_size
| 项目 | 说明 |
|---|---|
| 默认值 | 1073741824 字节(1GB) |
| 动态 | 是 |
undo 表空间的截断阈值。开启 innodb_undo_log_truncate 后,超过该值的 undo 表空间会被标记为可截断,最终截断到 16MB。
注意: 调大该值会减少截断频率,但会让 undo 占用更多磁盘;调小会增加截断频率和 I/O 开销。
六、监控与诊断
6.1 查看 History list length
sql
SHOW ENGINE INNODB STATUS\G
-- TRANSACTIONS 段:
-- Purge done for trx's n:o < 0 290315608 undo n:o < 0 17
-- History list length 15234
6.2 通过 INNODB_METRICS 查看 purge 指标
sql
SELECT NAME, SUBSYSTEM, COUNT, STATUS, COMMENT
FROM information_schema.INNODB_METRICS
WHERE NAME IN (
'trx_rseg_history_len',
'purge_del_mark_records',
'purge_upd_exist_or_extern_records',
'undo_truncate_count'
);
说明:
trx_rseg_history_len:history list 长度,最直接的指标,默认已启用 (属于transaction子系统);purge_del_mark_records:已 purge 的 delete-mark 记录数;purge_upd_exist_or_extern_records:已 purge 的更新记录数;undo_truncate_count:undo 表空间截断次数(属于undo子系统)。
后三个指标默认通常是关闭的,可以按模块开启:
sql
-- 开启 purge 子系统计数器
SET GLOBAL innodb_monitor_enable = 'module_purge';
-- 开启 undo 子系统计数器(8.0.16+ 提供 undo 截断相关计数器)
SET GLOBAL innodb_monitor_enable = 'module_undo';
注意:开启更多计数器会带来少量额外开销,排查完成后可以关闭:
SET GLOBAL innodb_monitor_disable = 'module_purge';
6.3 找出阻塞 purge 的长事务
sql
SELECT
trx_id,
trx_state,
trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS trx_seconds,
trx_mysql_thread_id,
trx_rows_modified,
trx_query
FROM information_schema.INNODB_TRX
ORDER BY trx_started
LIMIT 20;
重点关注 trx_seconds 很大、trx_state 为 RUNNING 或 LOCK WAIT 的事务。这类事务(包括只读的长事务)就是 purge 无法推进的常见原因。
6.4 查看 purge 相关线程和参数
sql
-- 查看后台线程(8.0)
SELECT NAME, TYPE, PROCESSLIST_ID
FROM performance_schema.threads
WHERE NAME LIKE '%purge%';
-- 查看参数当前值
SHOW VARIABLES LIKE 'innodb_purge%';
SHOW VARIABLES LIKE 'innodb_max_purge%';
SHOW VARIABLES LIKE 'innodb_undo%';
6.5 查看 undo 表空间文件
sql
SELECT TABLESPACE_NAME, FILE_NAME
FROM information_schema.FILES
WHERE FILE_TYPE LIKE 'UNDO LOG';
七、典型场景与调优案例
场景一:批量 DELETE 后 undo 表空间暴涨
现象: 业务做了一次历史数据清理,DELETE 了几百万行,之后 undo 表空间持续增长,History list length 长期在几十万以上。
排查思路:
- 确认是否有长事务 / 长查询卡住 read view;
- 确认 purge 线程数是否过小;
- 确认 undo 表空间是否开启了在线截断。
处理方式:
sql
-- 1. 先解决长事务(如果有)
-- 2. 适度提高 purge 批量(动态,立即生效)
SET PERSIST innodb_purge_batch_size = 1000;
-- 3. 如果确实需要更多 purge 线程,必须改配置并重启
-- my.cnf: innodb_purge_threads = 8
-- 重启后生效
-- 4. 评估 undo 表空间截断(注意性能开销)
-- 8.0 默认已开启;5.7 需要手动开启
SET GLOBAL innodb_undo_log_truncate = ON;
如果确认没有长事务,通常调整后 History list 会在几分钟内明显下降。
场景二:History list 不降,线程数调大也没用
现象: 把 innodb_purge_threads 从 4 调到 16(重启后生效),History list 依然不降。
原因: 这几乎可以确定是有活跃 read view 挡住了 purge。常见来源:
- 应用层开了事务但忘记提交 / 回滚;
- 某些连接长时间空闲但仍持有事务(
Sleep状态 + 未提交事务); - 从库上执行了长时间的大查询;
- 备份工具、逻辑导出工具持有了一致性快照(例如
mysqldump --single-transaction); - 关闭
autocommit后执行查询却忘记提交。
结论: 这种情况下调参数是无效的,必须先定位并终止长事务。可以通过 information_schema.INNODB_TRX 结合 performance_schema.threads、SHOW PROCESSLIST 找到源头。
场景三:单表热点导致 purge 跟不上
现象: 业务集中在少数几张表上,History list 持续增长,加 purge 线程也没有明显改善。
原因: 官方文档明确说明,单张表的 purge 只能由一个 purge 线程处理,线程再多也无法并行处理同一张表。
处理方式:
- 不要继续加线程,避免 purge 线程与用户线程争抢热点表;
- 考虑拆分热点表、分批处理大事务、错峰执行批量 DML;
- 评估
innodb_purge_threads是否应该保持较低值。
场景四:开启 undo 截断后业务出现卡顿
现象: 升级到 8.0 后(或手动开启 innodb_undo_log_truncate 后),业务出现偶发延迟抖动。
原因: undo 表空间截断本身有性能开销,官方也提示可能造成 stalls。
处理方式:
- 观察卡顿是否与 undo 截断时间点吻合(可开启
module_undo计数器); - 对延迟敏感的核心库,评估是否将
innodb_undo_log_truncate设为 OFF,改为按需手动截断; - 调整
innodb_max_undo_log_size,避免截断过于频繁; - 不要在业务高峰期做截断相关变更。
八、参数速查表
| 参数 | 默认值 | 范围 | 动态 | 是否需要重启 | 作用 |
|---|---|---|---|---|---|
innodb_purge_threads |
4(5.7/8.0);8.4 按 CPU 计算 | 1 ~ 32 | 否 | 是 | purge 线程数上限 |
innodb_purge_batch_size |
300 | 1 ~ 5000 | 是 | 否 | 每批处理的 undo page 总数 |
innodb_max_purge_lag |
0 | 0 ~ 4294967295 | 是 | 否 | History list 超过阈值时延迟 DML |
innodb_max_purge_lag_delay |
0 | 0 ~ 10000000 | 是 | 否 | DML 最大延迟(微秒) |
innodb_purge_rseg_truncate_frequency |
128 | 1 ~ 128 | 是 | 否 | 每 N 次 purge 尝试释放 rollback segment |
innodb_undo_log_truncate |
ON(8.0/8.4);OFF(5.7) | ON/OFF | 是 | 否 | 是否允许自动截断 undo 表空间 |
innodb_max_undo_log_size |
1073741824(1GB) | --- | 是 | 否 | undo 表空间截断阈值 |
innodb_undo_tablespaces |
2 | 2 ~ 127 | 已废弃 | --- | 8.0.14+ 不可配置 |
innodb_rollback_segments |
128 | 1 ~ 128 | 是 | 否 | 每个 undo 表空间的 rollback segment 数 |
九、生产变更检查清单
修改 purge 相关参数前,逐项确认:
- 已确认 MySQL 版本及该版本下参数的默认值、动态性。
- 已确认修改是否需要重启;如果需要重启,已安排维护窗口和主从切换预案。
- 已在测试环境验证参数效果和业务影响。
- 已备份原配置文件,记录修改前的参数值。
- 已确认当前是否存在长事务;如果存在,先处理长事务。
- 已确认 History list length 的基线值,便于对比。
- 已准备监控项:History list length、CPU、I/O、复制延迟、业务 P99。
- 已明确回滚步骤和回滚判定标准。
- 变更后持续观察至少 30 分钟,确认没有异常再结束变更。
十、总结
- purge 清理的是 update undo,INSERT 产生的 undo 不需要 purge,所以 History list 主要由 UPDATE / DELETE 推动。
- purge 的推进速度取决于最老的活跃 read view,长事务是 purge 最大的敌人,调参数解决不了长事务问题。
innodb_purge_threads是只读参数,修改必须重启;它控制的是线程数上限,实际线程数由 purge 系统自动调整。- 单表热点场景下,一张表只能由一个 purge 线程处理,加线程收益有限;8.0.26 起在超过
innodb_max_purge_lag时会自动重新分配 purge 工作。 innodb_purge_batch_size的单位是 undo page 而不是行 ,默认 300;多线程下是总预算按线程数拆分,不是每线程一份。innodb_max_purge_lag的本质是主动延迟 DML,生产环境慎用;如果 purge 被长事务卡住,开启它只会让业务更慢。innodb_undo_log_truncate在 8.0 默认开启,官方明确提示有持续性能开销、可能造成卡顿;对延迟敏感的库应评估是否关闭并改为手动截断。innodb_undo_tablespaces在 8.0.14+ 已废弃、不可配置,不要试图用它调优。- 排查 purge 问题,第一看 History list length,第二看
INNODB_TRX里的长事务,第三才考虑调参数。
一句话:先治长事务,再谈调参数;能动态改的先小步验证,需要重启的必须走变更流程。
参考资料
- MySQL 8.0 Reference Manual --- InnoDB Startup Options and System Variables:https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html
- MySQL 8.0 Reference Manual --- Purge Configuration:https://dev.mysql.com/doc/refman/8.0/en/innodb-purge-configuration.html
- MySQL 8.0 Reference Manual --- Undo Tablespaces(含 Truncating Undo Tablespaces):https://dev.mysql.com/doc/refman/8.0/en/innodb-undo-tablespaces.html
- MySQL 8.0 Reference Manual --- InnoDB INFORMATION_SCHEMA Metrics Table:https://dev.mysql.com/doc/refman/8.0/en/innodb-information-schema-metrics-table.html
- MySQL 8.0 Reference Manual --- Changes in MySQL 8.0(Valid Performance Regressions):https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html
- MySQL 8.4 Reference Manual --- InnoDB Parameters:https://dev.mysql.com/doc/refman/8.4/en/innodb-parameters.html