MySQL purge 参数详解:innodb_purge_threads 与 innodb_purge_batch_size

文章目录

    • 前言
    • 【生产环境重要提示】先读这一节
      • [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. 变更前必须确认的事项

  1. 确认 MySQL 版本:5.7、8.0、8.4 的默认值和动态性不同,不能照搬网上的参数值。
  2. 先在测试实例验证:尤其是只读参数,改完必须重启才能验证效果。
  3. 准备回滚方案:保留原配置文件,确认主从切换、连接重连、业务重试机制可用。
  4. 选择低峰期操作:重启和批量 purge 都会带来资源波动。
  5. 明确根因:如果是长事务阻塞 purge,调参数无效,必须先处理长事务。
  6. 持续观察: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 时:

  1. 把当前行的旧值写入 undo log;
  2. 将当前行标记为 delete-mark(逻辑删除);
  3. 插入一条新版本的行记录。

DELETE 则是把行标记为 delete-mark,并不立即物理删除。

purge 要做的,就是物理删除已经被 delete-mark 且不再被任何读视图需要的记录 ,同时回收不再需要的 undo 记录。

1.3 purge 的判定条件

一条 undo 记录能否被清理,取决于两个条件同时满足:

  1. 产生该 undo 的事务已经提交(或回滚);
  2. 当前没有任何活跃的 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 的流程(简化)

  1. 协调线程从 history list 中取出待处理的 undo page;
  2. 按照最老的活跃 read view 判断哪些 undo 记录可以清理;
  3. 把可清理的 undo page 分发给 worker 线程;
  4. worker 线程物理删除 delete-mark 记录、清理索引项、回收 undo page;
  5. 处理完一批(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 长期在几十万以上。

排查思路:

  1. 确认是否有长事务 / 长查询卡住 read view;
  2. 确认 purge 线程数是否过小;
  3. 确认 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 分钟,确认没有异常再结束变更。

十、总结

  1. purge 清理的是 update undo,INSERT 产生的 undo 不需要 purge,所以 History list 主要由 UPDATE / DELETE 推动。
  2. purge 的推进速度取决于最老的活跃 read view,长事务是 purge 最大的敌人,调参数解决不了长事务问题。
  3. innodb_purge_threads 是只读参数,修改必须重启;它控制的是线程数上限,实际线程数由 purge 系统自动调整。
  4. 单表热点场景下,一张表只能由一个 purge 线程处理,加线程收益有限;8.0.26 起在超过 innodb_max_purge_lag 时会自动重新分配 purge 工作。
  5. innodb_purge_batch_size 的单位是 undo page 而不是行 ,默认 300;多线程下是总预算按线程数拆分,不是每线程一份。
  6. innodb_max_purge_lag 的本质是主动延迟 DML,生产环境慎用;如果 purge 被长事务卡住,开启它只会让业务更慢。
  7. innodb_undo_log_truncate 在 8.0 默认开启,官方明确提示有持续性能开销、可能造成卡顿;对延迟敏感的库应评估是否关闭并改为手动截断。
  8. innodb_undo_tablespaces 在 8.0.14+ 已废弃、不可配置,不要试图用它调优。
  9. 排查 purge 问题,第一看 History list length,第二看 INNODB_TRX 里的长事务,第三才考虑调参数。

一句话:先治长事务,再谈调参数;能动态改的先小步验证,需要重启的必须走变更流程。

参考资料

相关推荐
云和数据.ChenGuang1 小时前
python sorted函数详细剖析
开发语言·人工智能·windows·python·1024程序员节
qq_355939491 小时前
标签打印发虚、断线、糊成一团?先分清是哪种“不清“
1024程序员节
三垣网安1 小时前
某某大学心理健康教育平台弱口令及SQL注入漏洞
数据库·sql
打工仔折腾 AI1 小时前
AI Agent 工程化实战(二):5 分钟跑通 Deep Agent 并逐层拆解底层链路
java·数据库·人工智能·后端·python·langchain·ai agent 实战
云和数据.ChenGuang1 小时前
Python map() 函数详解:从语法、原理到实战用法
开发语言·人工智能·python·langchain·1024程序员节
Shaoshing2 小时前
MySQL视图
数据库
AI视频工具指南2 小时前
一个人能做 AI 短剧吗?实际要做哪些决定
人工智能·aigc·音视频·1024程序员节
终端安全笔记2 小时前
下一个大版本要动的三处:更新指令、传输要求、策略写法
网络安全·ios·1024程序员节·移动设备管理
砚底藏山河2 小时前
python量化实操:回测数据缓存与重放
java·python·金融·1024程序员节