在 MySQL 8.0 的 InnoDB 存储引擎中,Page Cleaner Thread 是负责后台“刷脏页”(flush dirty pages)的关键组件。它直接关系到 Buffer Pool 的内存管理效率、I/O 负载分布以及整体数据库响应延迟。
1. 设计目的:为何需要 Page Cleaner Thread?
在早期 MySQL 版本(5.6 之前),刷脏页主要由 Master Thread 承担。这种集中式处理导致两个问题:
- 主线程负担过重,影响事务处理吞吐;
- 刷脏时机不可控,容易在高负载时引发突发 I/O 峰值(“flush storm”)。
为解决这些问题,MySQL 5.6 引入了 独立的 Page Cleaner Thread,并在后续版本(尤其是 5.7 和 8.0)中持续优化其并行能力与调度策略。其核心目标是:
- 解耦刷脏逻辑,避免阻塞用户线程;
- 平滑 I/O 负载,通过周期性、渐进式刷脏防止 I/O 突刺;
- 提升 Buffer Pool 利用率,确保有足够空闲页供新数据加载。
2. 工作机制详解
2.1 触发条件
Page Cleaner Thread 在以下两种场景下被激活:
- 定时刷新(background flush):每秒执行一次,根据 innodb_io_capacity 控制刷页速率;
- 紧急刷新(aggressive flush):当 Free List 低于阈值或 Redo Log 接近 checkpoint 限制时触发。
2.2 刷脏来源
Page Cleaner 主要从两个列表中选取脏页进行刷写:
- Flush List:按 oldest modification LSN 排序,用于推进 checkpoint;
- LRU List:淘汰冷数据页(包括干净页和脏页),释放 Buffer Pool 空间。
注意:从 MySQL 5.7 开始,LRU 刷脏也由 Page Cleaner 统一处理,不再依赖 Master Thread。
2.3 并行化支持
MySQL 8.0 支持多 Page Cleaner 线程(默认等于 Buffer Pool Instance 数量,上限为 64):
innodb_page_cleaners = 4 # 默认值,通常等于 innodb_buffer_pool_instances
每个线程负责一个 Buffer Pool Instance,实现真正的并行刷脏。
3. 与其他组件的协作关系
| Buffer Pool | Page Cleaner 从其 Flush List 和 LRU List 中选取脏页刷出 |
| Redo Log | 刷脏进度决定 oldest_modification LSN,进而影响 checkpoint 位置;若 Redo 空间不足,会触发紧急刷脏 |
| Master Thread | 在 MySQL 8.0 中,Master Thread 不再参与刷脏,仅负责其他后台任务(如 purge 调度) |
| I/O Scheduler | Page Cleaner 的刷页请求通过 异步 I/O(AIO) 提交,受 innodb_use_native_aio 影响 |
4. 工作流程
[启动 Page Cleaner Thread]
↓
[每秒检查是否需 background flush]
↓
是 → [计算本次可刷页数 = min(innodb_io_capacity, 脏页比例 × 总页数)]
↓
[从 Flush List 取脏页(推进 checkpoint)]
↓
[从 LRU List 尾部扫描 innodb_lru_scan_depth 页,刷出脏页]
↓
[提交 I/O 请求(异步)]
↓
[等待下一轮调度 或 被唤醒处理紧急 flush]
注:紧急 flush 由用户线程或 Redo Log 检查机制触发,会立即唤醒 Page Cleaner。
5. 关键参数对行为的影响
| innodb_io_capacity | 200 | 控制每秒后台 I/O 页数上限 | SSD 可设为 2000+;HDD 建议 200–500 |
| innodb_io_capacity_max | 2000 | 紧急 flush 时的最大 I/O 速率 | 通常设为 io_capacity 的 2–10 倍 |
| innodb_lru_scan_depth | 1024 | 每次 LRU 扫描的页数 | 过高增加 CPU 开销;SSD 可适当降低(如 256) |
| innodb_page_cleaners | 等于 buffer_pool_instances | 并行刷脏线程数 | 一般无需修改;确保 ≤ 实例数 |
| innodb_adaptive_flushing | ON | 动态调整刷脏速率 | 强烈建议保持开启 |
错误配置示例:将 innodb_lru_scan_depth 设为 10000 会导致 CPU 在 LRU 扫描上过度消耗,反而降低吞吐。
6. 性能影响与调优建议
- I/O 瓶颈:若 Innodb_buffer_pool_wait_free > 0,说明 Free List 不足,需提升刷脏效率(增大 io_capacity 或优化 lru_scan_depth)。
- Redo Log 压力:频繁出现 “Log sequence number is not up to date” 警告,表明刷脏跟不上 Redo 生成速度,应检查 Page Cleaner 是否受限。
- 监控指标:
- SHOW ENGINE INNODB STATUS 中的 BUFFER POOL AND MEMORY 部分;
主要看的内容
Modified db pages:当前 Buffer Pool 中的脏页数
若持续接近 innodb_max_dirty_pages_pct(默认 90%),说明刷盘跟不上写入
— 查看 Page Cleaner 线程是否被阻塞
SELECT THREAD_ID, NAME, PROCESSLIST_ID, TYPE
FROM performance_schema.threads
WHERE NAME LIKE '%page_cleaner%';
— 查看其等待事件(如 io wait)
SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT
FROM performance_schema.events_waits_summary_by_thread_by_event_name
WHERE THREAD_ID = <Page_Cleaner_Thread_ID>
ORDER BY SUM_TIMER_WAIT DESC;
数据来源:
SHOW GLOBAL STATUS LIKE 'Innodb_pages_written';
SHOW GLOBAL STATUS LIKE 'Innodb_data_writes';
含义解析:
| Innodb_pages_written | InnoDB 逻辑写入的页数(每页 16KB) |
| Innodb_data_writes | InnoDB 实际发起的物理 I/O 次数(每次可写多页) |
计算比值:
平均每次 I/O 写入的页数 = Innodb_pages_written / Innodb_data_writes
理想值 & 问题判断:
| ≥ 8 | 优秀!I/O 合并效果好,Page Cleaner 批量刷盘高效 |
| 4 ~ 8 | 一般,可接受 |
| < 4 | 警告!I/O 合并差,可能原因: – innodb_io_capacity 设置过低 – 磁盘随机写性能差 – 脏页分布太分散(如大量小事务) – Page Cleaner 线程不足或被阻塞 |
| ≈ 1 | 严重问题!几乎每次只写 1 页,I/O 效率极低,会成为性能瓶颈 |
优化建议:
- 提高 innodb_io_capacity(建议设为磁盘 IOPS 的 70~80%);
- 确保 innodb_page_cleaners ≥ innodb_buffer_pool_instances;
- 使用 SSD 并启用 innodb_flush_method=O_DIRECT。
7. 面试题
问题一:
Page Cleaner Thread 和 Master Thread 在刷脏页上的职责有何区别(以 MySQL 8.0 为例)?
答:在 MySQL 8.0 中,Master Thread 不再执行任何刷脏操作。所有后台刷脏(包括 Flush List 和 LRU List)均由 Page Cleaner Thread 负责。这一设计实现了刷脏逻辑的完全解耦,提升了并发性和 I/O 可预测性。
问题二:
为什么调高 innodb_lru_scan_depth 不一定能提升性能?可能带来什么副作用?
答:innodb_lru_scan_depth 控制每次 LRU 扫描的页数。过高的值会导致:
- CPU 开销增加(需遍历更多页判断是否脏/可淘汰);
- 无效扫描增多(若 Buffer Pool 命中率高,尾部多为热页,无法淘汰);
- 反而降低吞吐。建议在 SSD 环境下适当降低该值(如 256–512)。




