非唯一索引更新能绕过页加载,是因为innodb在引擎层检查到索引非唯一且目标页不在buffer pool时,直接将变更写入change buffer并记入redo log,避免随机i/o;唯一索引因需即时校验唯一性而必须加载页。

因为非唯一普通索引更新时,InnoDB能跳过磁盘随机读页,把变更暂存在内存+redo中,等真正需要读该页时才合并——这直接把多次随机I/O压成一次顺序I/O。
非唯一索引更新为何能绕过页加载?
执行器调用 ha_innobase::update_row() 后,InnoDB在引擎层检查两个硬条件:索引定义不含 UNIQUE 关键字,且目标索引页未在 Buffer Pool 中。两者同时满足,就拒绝加载磁盘页,转而将变更(如 DELETE-MARK 或新键值)写入 Change Buffer 内存结构,并同步记入 redo log。
- 唯一索引必须立即校验冲突,强制触发页加载 → 随机读不可避免
- 普通索引无此约束,只要保证最终一致性即可 → “延迟加载+延迟合并”成立
- 哪怕业务逻辑上字段实际不重复,只要 DDL 里没声明
UNIQUE,InnoDB 就认作可走 Change Buffer
innodb_change_buffering 参数怎么设才真正生效?
该参数控制哪些操作类型进入 Change Buffer,默认值 all 表示 INSERT、DELETE-MARK、PURGE 全开。但注意:它只对非唯一二级索引起作用,对主键或唯一索引完全无效,改这个参数不会让唯一索引“变快”。
- 若业务写多查少、且大量 UPDATE/DELETE 涉及普通索引,保持
all最合理 - 设为
inserts会禁用 UPDATE/DELETE 的缓存,但 INSERT 仍可用 —— 这在某些强一致性场景下可能被误用 - MySQL 5.6+ 统一叫 Change Buffer,不存在独立的 “Insert Buffer” 配置项
Change Buffer 合并(merge)的时机有哪些?
变更不是永远躺在内存里,它会在以下任一时刻被应用到真实索引页:
- 某次 SELECT 触发该索引页加载进 Buffer Pool → 立即 merge,保证查询看到最新状态
- 后台线程
ibuf_thread定期扫描(默认每秒一次),合并空闲页上的 pending 变更 - Buffer Pool 内存紧张,某页即将被淘汰前,若其有未 merge 的变更,强制先 merge 再刷盘
- MySQL 正常关闭前,所有未 merge 项必须落盘,否则重启后无法恢复
这意味着:两次快速 UPDATE 同一条记录的普通索引字段,如果中间没任何查询访问该页,它们在 Change Buffer 中可能被合并为单次物理修改 —— 但执行器只看到两次独立成功的 DML 返回。
真正容易被忽略的是:Change Buffer 的收益高度依赖“页不在内存中”这一前提。如果 Buffer Pool 足够大、热数据全驻留,或者业务写完立刻查(强制触发 merge),那它不仅不加速,反而多了一次内存写+redo写+后续 merge 开销。











