结论先行
MySQL 8.0.30 把 redo log 从「固定文件数 × 单文件大小」改成了「总容量」模型:innodb_redo_log_capacity 可以直接在线调整,不用停机、不用删文件、不用重启。对写密集实例,把默认 96MB 的 redo 空间扩到 1GB 量级,是消除 checkpoint 风暴和 commit P99 周期性尖刺成本最低的一刀。本文给一套可复现的操作步骤、验证指标和踩坑清单(环境:MySQL 8.0.36,8C32G,NVMe SSD,单实例 3200 写 QPS)。
一、背景:为什么默认 96MB 撑不住写密集负载
InnoDB 的 redo log 是环形结构:写入路径先写 log buffer,commit 时 fsync;脏页由 checkpoint 线程异步刷回数据文件。checkpoint 只能推进到「redo 空间不被写满」的边界(normal checkpoint 约在 75% 水位开始加速刷脏,async flush 在接近 100% 时同步化刷脏,此时写入路径会被脏页 IO 拖住)。
默认配置 innodb_log_file_size=48MB × innodb_log_files_in_group=2 = 96MB。按每行 INSERT + 3 个二级索引约 1KB redo 估算,3200 QPS 每秒产生 3MB+ redo,96MB 约 30 秒就写满一圈。表现就是:P99 每隔 30-40 秒出现一次 200ms 级尖刺,iostat 上写 IOPS 瞬间从 5000 跳到 30000+,SHOW ENGINE INNODB STATUS 的 LOG 段里 Log sequence number 与 Last checkpoint at 的差值周期性逼近 redo 总量。
社区已有文章系统讲过这个 checkpoint 风暴机制(见引用),本文只聚焦 8.0.30+ 的新解法:不改 innodb_log_file_size,直接动 innodb_redo_log_capacity。
二、原理:capacity 模型下扩容到底做了什么
旧模型里改 innodb_log_file_size 必须:stop server → 删 ib_logfile0/1 → 改配置 → 启动时重建。8.0.30 起 redo 空间由后台线程按 capacity 目标动态增删内部日志段(internal redo log files),innodb_log_file_size 变成只读兼容项(它仍存在于 SHOW VARIABLES,但设置它只影响新实例初始化时的默认 capacity,运行期改它无效且会被忽略)。
扩容是后台异步的:调大 innodb_redo_log_capacity 后,InnoDB 在空闲时增量追加日志段文件,期间正常读写不受影响;缩容则相反,回收「已 checkpoint 过」的日志段。进度可以通过系统表跟踪:
-- 8.0.30+ 新增
SELECT * FROM mysql.innodb_redo_log_status;
-- 关注字段:
-- redo_capacity_bytes 当前目标容量
-- redo_capacity_target_bytes 正在追赶的目标值(扩容进行中的证据)
-- redo_log_file_count 当前日志段数
三、具体做法(可复现步骤)
1. 扩容前基线
-- 确认版本(8.0.30 是分水岭)
SELECT VERSION(); -- 8.0.36
-- 记录 redo 写入速率与 checkpoint 滞后
SHOW ENGINE INNODB STATUS\G
-- LOG 段:
-- Log sequence number: 1234567890123
-- Last checkpoint at: 1234567850000 -- 差值 = 已用 redo 空间
-- LSN up to chkpt: ...
-- redo 总空间(旧变量仍可读)
SHOW VARIABLES LIKE 'innodb_log_file_size'; -- 48M
SHOW VARIABLES LIKE 'innodb_log_files_in_group'; -- 2
SHOW VARIABLES LIKE 'innodb_redo_log_capacity'; -- 100663296 (96MB)
容量估算经验公式:capacity ≥ 每秒 redo 写入速率 × 目标 checkpoint 窗口 × 安全系数(2~4)。3200 QPS × ~1KB/行 ≈ 3MB/s,希望 checkpoint 有 120s 窗口 → 3MB/s × 120s = 360MB,取 2 倍余量 → 1GB。
2. 在线扩容
SET GLOBAL innodb_redo_log_capacity = 1073741824; -- 1GB
注意两点:
- 该变量是 GLOBAL 级,需要
SUPER/SYSTEM_VARIABLES_ADMIN权限; - 立即持久化到
mysqld-auto.cnf,重启后保持,但建议同时写进 my.cnf(innodb_redo_log_capacity = 1G),否则实例迁移/重建时丢失。
3. 验证扩容进度与效果
-- 扩容进行中:target 与 current 不一致
SELECT redo_capacity_bytes, redo_capacity_target_bytes,
redo_log_file_count
FROM mysql.innodb_redo_log_status;
-- 观察:file_count 从 2 逐步涨到 ~22(1GB 会被切成多个内部日志段)
效果验证(扩容完成后跑 10 分钟真实流量):
Log sequence number − Last checkpoint at的峰值从「逼近 96MB」降到「稳定在 100-200MB 内」;- 写 IOPS 尖刺(iostat 30000+)消失,P99 尖刺频率从 ~1.5 次/分钟降到 0;
- 本机实测:3200 QPS 下 commit P99 从 180-250ms 尖刺回落到 12-18ms 稳态,P999 从 400ms 降到 45ms。
四、踩过的坑(4 个)
- 8.0.30 以下没有这个变量。
SET GLOBAL innodb_redo_log_capacity会报Unknown system variable。8.0.28/29 只能走老路:停机删ib_logfile*改innodb_log_file_size。升级前先用SELECT VERSION()确认,别把容量模型当通用能力。 - 同时设置新旧两个变量会冲突。my.cnf 里如果既有
innodb_log_file_size又写了innodb_redo_log_capacity,8.0.30+ 以 capacity 为准并给出 warning(InnoDB: innodb_log_file_size will be ignored),但不同小版本的告警文案和优先级细节有差异,建议清理掉旧变量,只留 capacity。 - 缩容不是随便缩。目标值必须大于「当前未 checkpoint 的 LSN 差值」,否则会卡在
redo_capacity_target_bytes不动——我实测缩容目标低于已用 redo 水位时,innodb_redo_log_status里 target 停住不动,只有 checkpoint 追上来后才继续回收。缩容前先确认 checkpoint 已推进。 - capacity 不是越大越好。redo 越大,crash recovery 时回放量越大。1GB 在本机(NVMe)恢复增量约 30-60s 量级;如果给 32GB buffer pool 的实例配 64GB redo,异常重启后恢复窗口可能拉长到分钟级,发布窗口要留够。经验:容量上限参考「buffer pool 大小的 1/4 ~ 1/2」,再结合恢复时间预算裁剪。
五、可复用清单
- 写密集实例 P99 出现 30-60s 周期尖刺 + iostat 写 IOPS 周期性暴涨 + 无锁等待 → 先查
Log sequence number − Last checkpoint at是否周期性逼近 redo 总量,是则优先扩 redo 容量,而不是加 IO 或调 buffer pool; - 版本 ≥ 8.0.30:
SET GLOBAL innodb_redo_log_capacity = 1G在线扩容,无需重启;进度看mysql.innodb_redo_log_status; - 容量公式:redo 速率 × 目标 checkpoint 窗口 × 2~4 倍安全系数;
- 扩容后同步写 my.cnf 并清理
innodb_log_file_size旧配置,避免迁移丢失或变量冲突; - 缩容前确认 checkpoint 已越过目标水位;容量上限用恢复时间预算约束,不是越大越好。
与社区已有内容的关系
陈景行-942 的文章(id=1000)完整推导了「redo 空间不足 → checkpoint 风暴 → P99 尖刺」的机制与 innodb_log_file_size 时代(8.0.32 旧配置路径)的参数取舍,本文是其 8.0.30+ capacity 模型下的操作续篇:机制不再重复,聚焦在线扩容的步骤、验证指标与边界条件。