Change Buffer 的核心作用是避免随机读 I/O,而非减少随机写 I/O;它通过将非唯一二级索引页的变更暂存于内存 B+ 树,跳过“先读页再修改”的昂贵磁盘读取步骤,仅写内存和 redo log,待后续访问页或后台空闲时批量合并,从而提升写性能。

Change Buffer 不是“减少随机写 IO”,而是**避免随机读 IO**——这是最常被搞反的关键点。它不加速磁盘写,而是省掉一次或多次从磁盘把二级索引页读进内存的昂贵操作。
为什么说 Change Buffer 解决的是随机读,不是随机写?
InnoDB 的聚簇索引(主键索引)更新通常能利用 Buffer Pool 缓存,但非唯一二级索引页分布离散,修改时往往不在内存里。传统流程是:
- 发现
idx_name页不在 Buffer Pool → 触发一次随机磁盘读(代价高) - 读入后修改页 → 写 redo log → 刷脏页回磁盘
Change Buffer 把这个“先读再改”的链路截断了:页没在内存?那就把变更(insert、delete-mark、update)记到自己的 B+ 树结构里,只写内存 + redo log,跳过那步随机读。
真正写磁盘的动作,等后续该页被读取(比如某条 SELECT ... WHERE name = ? 触发加载)或系统空闲时批量合并(merge),此时变成顺序或局部集中写,I/O 效率高得多。
innodb_change_buffering 开关控制哪些操作走 Change Buffer
默认值是 all,但并非所有 DML 都生效——它只对**非唯一二级索引**起作用。唯一索引(含主键)必须实时校验唯一性,无法延迟合并,所以不进 Change Buffer。
可选值包括:
-
all(默认):insert、delete-mark、delete全部缓存 -
inserts:仅缓存插入 -
none:完全禁用(调试或 OLAP 场景偶尔用)
注意:delete-mark 是标记删除(行未物理移除),delete 是清除已标记的记录——两者都支持缓存,但实际合并时机不同。
什么时候 merge 发生?别指望它“立刻”应用
Change Buffer 的变更不会自动刷到索引页上,依赖以下任一触发条件:
- 该索引页被主动读取(例如查询命中该页)
- 后台线程在空闲时扫描并合并(
innodb_io_capacity影响频率) - Change Buffer 自身空间不足(
innodb_change_buffer_max_size默认 25%,超限强制 merge) - MySQL 正常关闭前(保证数据不丢)
这意味着:如果你有一张冷表,只写不查,Change Buffer 里的变更可能长期滞留——看起来“写得快”,但磁盘上的索引其实没更新。重启后首次查这张表,会卡在 merge 阶段,造成明显延迟。
监控 SHOW ENGINE INNODB STATUS 里的真实状态
光看配置没用,得看运行时数据。执行命令后关注这段:
------------------------------------- INSERT BUFFER AND ADAPTIVE HASH INDEX ------------------------------------- Ibuf: size 1, free list len 4289, seg size 4291, 127345 merges merged operations: insert 89231, delete-mark 35672, delete 2442 discarded operations: insert 17, delete-mark 9
关键字段含义:
-
merges:越大说明 Change Buffer 越活跃,也意味着你有大量二级索引写入 -
discarded operations:被丢弃的操作,通常是页在 merge 前被 purge 或重用,数值持续增长需警惕 -
free list len和seg size差值小 → Change Buffer 几乎满载,可能拖慢写入吞吐
真正影响性能的,不是“有没有启用”,而是“merge 是否成为瓶颈”。尤其当 merges 每秒突增数百次,且伴随 innodb_buffer_pool_wait_free 上升,说明后台 merge 线程跟不上写入节奏,索引页更新滞后,查询开始变慢。
Change Buffer 的价值高度依赖工作负载特征:写多查少、二级索引更新频繁、Buffer Pool 不足以缓存全部索引页——缺一不可。盲目调大 innodb_change_buffer_max_size 可能挤占 Buffer Pool,反而让热数据换出,得不偿失。


















