insert buffer能合并写io是因为它将对同一索引页的多次修改暂存于内存b+树中,待该页被读取时批量合并落地,把n次随机写聚合成1次顺序整页写。

Insert Buffer 为什么能合并写 IO
因为它的核心不是“缓存写请求”,而是把对同一索引页的多次修改攒起来,等实际读取该页时再一次性合并落地——本质是把离散的随机写,推迟并聚合成顺序写。
InnoDB 的辅助索引页(secondary index page)物理上分散在磁盘各处。如果每次插入都立刻定位、读取、修改、刷盘,就是大量 random I/O。而 Insert Buffer 把这些操作先记在一个内存 B+ 树里(后来演进为 change buffer),结构按 (space_id, page_no, counter) 组织,天然支持按页聚合。
- 同一
page_no的多条INSERT记录,在change buffer中被归到同一个节点下 - 当该页因查询被加载进
buffer pool时,InnoDB 自动触发merge:把 buffer 中所有对该页的变更(INSERT/DELETE/UPDATE)批量应用到内存页 - 后续刷盘时,这个页只写一次——相当于把 N 次小写合并成 1 次整页写,且写入位置连续
什么情况下无法合并?
只要触发条件不满足,Insert Buffer 就退化为直写,失去合并能力。
- 索引是
UNIQUE:必须立即查页判断唯一性,强制触发random read,change buffer被跳过 - 索引是
PRIMARY KEY或CLUSTERED INDEX:数据本身就在聚集索引页中,更新即改数据页,不走change buffer - 页已存在于
buffer pool:直接修改内存页,绕过缓冲区 - 执行
SELECT查询该索引:会强制触发merge,提前释放缓冲,不再积累
merge 是谁触发的?什么时候发生?
不是定时任务,而是由真实访问驱动的“懒合并”——没有后台线程主动刷,全靠业务行为触发。
-
SELECT扫描或查找某二级索引页时:InnoDB 先检查change buffer是否有对该页的 pending 修改,有则合并后再返回结果 -
page cleaner thread在刷脏页前:若发现该页关联的change buffer记录未清空,会一并 merge 后再刷盘 - 系统空闲时
master thread(MySQL 5.6+ 已弱化)可能做少量合并,但不可依赖 - 实例重启后恢复阶段:所有未 merge 的
change buffer记录会被重放,此时是串行、阻塞式 merge,可能很慢
容易被忽略的代价
合并 IO 看似免费,但延迟和内存开销是实打实的。
-
change buffer占用的是buffer pool内存,默认最多吃掉 1/2;高写入场景下可能挤占热数据页空间 - 大量未 merge 的记录堆积时,
SELECT响应时间会突然变长——因为要现场做 merge + 查找 - 崩溃恢复时间取决于未 merge 数据量,
innodb_change_buffer_max_size设太高,可能让 recovery 超过分钟级 - 监控要看
Innodb_buffer_pool_pages_misc和Innodb_change_buffer_*状态变量,而不是只盯buffer pool hit rate











