一、InnoDB 内存结构全景回顾
InnoDB 内存结构
├── Buffer Pool 数据页/索引页缓存(上一篇)
├── Change Buffer 二级索引变更缓存(本篇)
├── Adaptive Hash Index 自动哈希索引(本篇)
├── Log Buffer redo log 内存缓冲(本篇简述,磁盘部分见 redo 专篇)
└── Lock Sys / 其他 锁系统等(本篇不展开)
加粗要点 :Change Buffer 的缓存数据占用 Buffer Pool 空间,但它是独立逻辑子系统;AHI 是建立在 Buffer Pool 页上的派生结构
二、Change Buffer(变更缓冲区)
2.1 它解决什么问题
修改一条数据时,如果对应的二级索引页不在 Buffer Pool:
朴素流程:
① 随机读磁盘:把二级索引页读进 Buffer Pool
② 修改该页,变成脏页
③ 以后再随机写回磁盘
→ 一次修改 = 至少两次随机 IO
Change Buffer 的思路:不立即读那个二级索引页,把变更操作先缓存起来;等以后这个页真的被访问(查询加载)时,再把缓存的变更批量合并进去。
2.2 原理
一条 UPDATE/DELETE/INSERT 语句:
① 聚簇索引(主键):立即修改。因为聚簇索引叶子 = 数据行本身,必须立即写。
② 二级索引:
├─ 索引页已在 Buffer Pool → 直接修改,不经过 Change Buffer
└─ 索引页不在 Buffer Pool → 把这条变更记录缓存到 Change Buffer
(不立即从磁盘读页)
加粗要点 :只有"二级索引页不在内存"时才缓存;而且聚簇索引的修改永远是立即执行的。
2.3 为什么只缓存二级索引?
| 索引类型 | 叶子节点存什么 | 能否延后修改 |
|---|---|---|
| 聚簇索引(主键) | 数据行本身 | ❌ 必须立即改。插入一行数据,你必须知道它落在哪个数据页,这行数据就是页的内容 |
| 二级索引 | 索引列值 + 主键值(冗余结构) | ✅ 可以延后。它是辅助查询的冗余副本,查询不走它就不影响结果 |
核心原因:聚簇索引 = 数据本身,不能延后;二级索引 = 冗余副本,可以慢慢维护。你插入一行新数据,必须立即把数据写到聚簇索引的叶子页(这是真实数据);但二级索引页暂时不在内存,先记一笔账,等以后真要查这个索引时再合并就行。
2.4 合并时机
变更记录什么时候真正合并到二级索引页?
① 当查询把这个二级索引页从磁盘加载进 Buffer Pool 时(顺带合并)
② 系统正常关机时
③ Change Buffer 空间即将耗尽时(后台线程强制 merge)
④ 后台 master thread 定期合并
2.5 配置:innodb_change_buffering
SET GLOBAL innodb_change_buffering = all;
-- 或在 my.cnf 中配置
| 取值 | 缓存什么 |
|---|---|
all(默认) |
inserts、delete marking、purges |
none |
什么都不缓存 |
inserts |
只缓存插入 |
deletes |
只缓存删除标记(delete marking) |
changes |
inserts + delete marking |
purges |
只缓存后台物理删除操作 |
删除标记是什么?:
这里要区分两个阶段:
- delete marking(删除标记) :UPDATE/DELETE 时,给记录头打一个 deleted 标记,记录逻辑上删了,但物理上还在页里(空间没释放);
- purge(清除) :后台线程把这些"已标记删除"的记录真正从页里物理移除,释放空间。
所以:
deletes缓存的是"打删除标记"这个动作;purges缓存的是"真正物理清除"这个动作;- 两者是删除流程的两个阶段。
三、Adaptive Hash Index(自适应哈希索引)
3.1 是什么
AHI 是 InnoDB 自动根据访问模式建立的哈希索引------不需要 DBA 手动创建。
没有 AHI 时,一次等值查询:
where id = 100
→ 从 B+Tree 根页开始,逐层往下找
→ 经过好几层目录页(可能不在内存,要读磁盘)
有 AHI 命中时:
where id = 100
→ InnoDB 自动对频繁访问的索引键建了哈希
→ 直接通过哈希定位到数据页地址,跳过 B+Tree 层级遍历
3.2 关键特性
| 特性 | 说明 |
|---|---|
| 自动建立/拆除 | InnoDB 监测访问模式,某页被频繁等值查询就建哈希;不热了自动拆 |
| 只支持等值查询 | =、<=>、IN;不支持范围查询(BETWEEN、>、<) |
| 占用 Buffer Pool 空间 | 哈希表本身在 Buffer Pool 内 |
| 分区减少锁竞争 | innodb_adaptive_hash_index_parts 控制分区数 |
3.3 配置
-- 是否开启 AHI(默认 ON)
SHOW VARIABLES LIKE 'innodb_adaptive_hash_index';
-- AHI 分区数(默认 8,并发越高锁竞争越少)
SHOW VARIABLES LIKE 'innodb_adaptive_hash_index_parts';
四、Log Buffer(日志缓冲区)
Log Buffer 是 redo log 的内存缓冲区:
事务修改数据时:
① 先把 redo log 记录写到 Log Buffer(内存)
② 按策略(always/everysec)刷到磁盘 redo log 文件
加粗要点 :Log Buffer 的内存部分 就这一点;redo log 的磁盘格式、刷盘策略(innodb_flush_log_at_trx_commit)、崩溃恢复流程,在后续 redo log 专篇详细讲。
五、三种内存结构对比
| 结构 | 作用 | 占用哪块内存 | 关键点 |
|---|---|---|---|
| Change Buffer | 缓存二级索引变更,省随机 IO | Buffer Pool 内 | 只缓存二级索引页不在内存的情况;聚簇索引立即改 |
| AHI | 自动哈希索引,加速等值查询定位 | Buffer Pool 内 | InnoDB 自动建/拆;只支持等值查询 |
| Log Buffer | redo log 内存缓冲,崩溃恢复用 | 独立内存区 | 磁盘部分见 redo 专篇 |
查看运行状态:
SHOW ENGINE INNODB STATUS\G
-- 里面能看到 CHANGE BUFFER、ADAPTIVE HASH INDEX、LOG 等段落
六、总结
- Change Buffer:二级索引页不在内存时,先缓存变更,等页被加载时合并;聚簇索引必须立即改;
- 为什么只缓存二级索引:聚簇索引=数据本身,不能延后;二级索引是冗余副本;
- 合并时机:页被查询加载、关机、空间耗尽、后台定期 merge;
- delete marking vs purge:一个是打删除标记,一个是物理清除;
- AHI:InnoDB 自动建的哈希索引,只加速等值查询;
- Log Buffer:redo log 内存缓冲,磁盘机制见专篇。