MySQL InnoDB 缓存池(Buffer Pool)详解:原理、结构与链表管理
目录
- 为什么要缓存池
- 缓存池的基本原理
- 缓存池里都有什么
- 缓存页与控制块
- 缓存池管理:三条链表
- 读加速:哈希查找与命中率
- 写加速:脏页与刷盘
- [LRU 的进一步优化(扩展)](#LRU 的进一步优化(扩展))
- 小结
一、为什么要缓存池?
缓存池(Buffer Pool) 是 InnoDB 在内存中划出的一片区域,用来存放页(Page),本质上是对数据页的缓存。
- InnoDB 是磁盘型 存储引擎,数据以页为单位与磁盘打交道。
- CPU 与磁盘之间存在巨大的速度差;若每次访问都读盘,数据库性能会被磁盘延迟严重拖累。
- 因此引入缓存池:用内存的高速 尽量弥补磁盘的慢,让热数据常驻内存,降低对业务延迟的影响。
一句话:缓存池就是用内存换时间、缓解磁盘瓶颈的关键区域。
二、缓存池的基本原理
2.1 读操作
- 需要读取某一页时,若该页尚不在内存,则从磁盘读入,并放入缓存池。
- 再次访问同一页 时,优先判断该页是否已在缓存池中,命中则不必再读盘。
2.2 写操作
- 对数据页的修改,首先在缓存池中的页副本上进行。
- 修改不会立刻 全部写回磁盘,而是按一定策略、一定频率通过刷盘机制(与 Checkpoint 等机制配合)落盘。
- 整体上,读写路径都尽量围绕缓存池展开,而不是对磁盘做频繁的同步随机写。
扩展:Redo Log 保证崩溃恢复;数据页异步刷盘是吞吐与持久化之间的折中,下文「脏页」一节会呼应这一点。
2.3 缓存池在内存中的形态
- Buffer Pool 是一段连续的内存空间(逻辑上连续;实际可能按 Chunk 向 OS 申请)。
- InnoDB 以页为单位管理这片空间。
三、缓存池里都有什么?
笔记中的结构图将 Buffer Pool 内的信息分为多类,常见归纳如下:
| 类型 | 说明 |
|---|---|
| 数据页 | 表数据所在页 |
| 索引页 | B+ 树等索引结构页 |
| 插入缓冲 / Change Buffer | 对二级索引 的变更在对应索引页未进缓存时,可先缓冲合并(MySQL 官方文档中多称 Change Buffer,早期资料常写「插入缓冲」) |
| 自适应哈希索引(AHI) | InnoDB 对部分热点查询路径自动建立的哈希加速结构 |
| 锁信息 | 行锁等相关的内存结构信息 |
| 数据字典信息 | 表结构、元数据等 |
实务要点 :在绝大多数实例里,数据页 + 索引页占 Buffer Pool 的绝大部分空间;其余为辅助结构与控制开销。
接着的自然问题 :这么多页,InnoDB 如何管理?------答案是:为每个缓存页维护「控制信息」 ,即 缓存页控制块(Buffer Control Block)。
四、缓存页与控制块
4.1 控制块里有什么?
为管理缓存页,InnoDB 为每个页建立控制块,典型包含:
- 表空间 ID(Tablespace ID)
- 页号(Page Number)
- 该页在 Buffer Pool 中的地址
- 锁相关信息
- LSN(与重做日志、刷盘顺序相关)等信息
- 其它控制字段
4.2 控制块与缓存页的布局关系
- 每个控制块与每个缓存页 一一对应。
- 在 Buffer Pool 的内存布局上,常见描述为:前部存放控制块,后部存放真正的缓存页 ,中间可能存在无法凑齐「完整控制块 + 完整页」的间隙,称为碎片空间。
Buffer Pool 内存布局(示意)
控制块区
控制块1...n
碎片空间
缓存页区
缓存页1...n
控制块 1 对应缓存页 1,以此类推(与笔记中的箭头示意一致)。
五、缓存池管理:三条链表
笔记强调:管理缓存池依赖多种链表结构 。核心有三类:空闲链表(Free List) 、LRU 链表 、Flush 链表。
5.1 空闲链表(Free List)
- 作用 :登记当前空闲、可用来装载新读入页的缓存页。
- 结构 :双向链表;每个结点对应一块当前未承载有效磁盘页副本的缓存区域(实现上通过指向「缓存页控制块」再关联到具体缓存页,与笔记描述一致)。
- 类比(笔记):可类比 G1 等收集器对空闲 Region 的管理思路------用链表把「空闲单元」串起来,按需分配。
- 启动时 :分配好 Buffer Pool 后,会为「控制块 + 缓存页」成对初始化;此时还没有真实磁盘页载入,所有可用于承载数据的页都可视为空闲,全部挂入 Free 链表。
- 元数据 :为维护该链表,另有控制信息(头指针、尾指针、当前结点个数等)。
ctl 指向
Free 双向链表(示意)
结点: ctl / pre / next
...
...
Free 链表控制信息
start 头
end 尾
count 结点个数
控制块
空闲缓存页
5.2 LRU 链表
- 作用 :管理已经读进 Buffer Pool、参与替换决策的页(笔记:管理「已读入」的页)。
- 典型过程 :启动时 LRU 为空;需要读盘时,从 Free 链表 取一个空闲承载单元,读入磁盘页后,将该页纳入 LRU 链表管理。
- 当 Free 链表耗尽、需要为新页腾位置时,会结合 LRU 策略淘汰页,回收的承载单元可重新回到 Free 链表(笔记示意图:Free ↔ LRU 之间有来回箭头)。
5.3 Flush 链表(刷脏链)
- 作用 :管理脏页------已在内存中修改、但尚未刷回磁盘的页。
- 与 LRU 的关系(重点) :
- 脏页的实体数据仍在 LRU 链表所管理的缓存页中。
- Flush 链表结点保存的是指向这些脏页的指针/引用,而不是另一份页数据。
- 第一次 对某缓存页做修改导致变脏时,将该页加入 Flush 链表;同页再次修改 只更新 LRU 中的页内容,不会在 Flush 链表中重复插入(笔记要点)。
Buffer Pool 逻辑关系(示意)
读入新页
淘汰回收
Flush:脏页子集
Flush 链表指针
Free 空闲页
LRU 已载入页
脏页同时在 LRU 上;Flush 只维护刷盘顺序/指针
六、读加速:哈希查找与命中率
6.1 为什么需要哈希?
若每次判断「某页是否在 Buffer Pool」都线性扫描 LRU,代价过高。InnoDB 使用 哈希表 做近似 O(1) 的查找:
- Key :
表空间号 + 页号(笔记中的「表空间号、页号」) - Value :对应缓存页的指针(或能定位到控制块/缓存页的句柄)
6.2 读路径(与笔记一致)
- 先按(表空间号,页号)查哈希表。
- 命中:直接从 LRU 管理的缓存区读,避免磁盘 I/O。
- 未命中 :从 Free 链表取空闲结点,从磁盘读入页,加入 LRU,并更新哈希表。
6.3 缓存命中率(笔记中的直观公式)
用访问次数来理解:
\\text{命中率} \\approx \\frac{\\text{在缓存中命中的访问次数}}{\\text{总访问次数 } n}
命中率越高,磁盘读越少。生产环境可结合 SHOW GLOBAL STATUS 中的 Innodb_buffer_pool_read_requests / Innodb_buffer_pool_reads 等指标做量化观察。
七、写加速:脏页与刷盘
7.1 写路径(与笔记一致)
- 写操作先在缓存池中的页 上完成,并写 Redo Log (重做日志);从事务持久化角度,日志落盘策略配合 WAL 保证可恢复性。
- 内存中已修改、尚未刷回数据文件的页叫 脏页(Dirty Page)。
- 后台线程负责在适当时机把脏页刷到磁盘,避免每次更新都同步写数据页。
7.2 何时刷、按什么顺序刷?(笔记核心)
- InnoDB 不会在每次修改后立刻把数据页同步刷盘(否则写放大与延迟都会很差)。
- 脏页通过 Flush 链表组织;刷盘由后台线程异步进行。
- 顺序 :与页的修改时序相关,常按 oldest_modification / oldest_lsn 等(笔记中的 oldest_lsn 表述)组织------越早产生修改的脏页,越优先被刷的倾向,有利于推进检查点、缩小恢复范围(具体实现随版本有细节差异,面试答「按最老修改 LSN 组织刷脏」即可与笔记对齐)。
扩展 :
innodb_max_dirty_pages_pct、innodb_flush_neighbors(高版本逐步弱化)、innodb_io_capacity等会影响刷脏节奏,运维时需结合磁盘能力与延迟监控。
八、LRU 的进一步优化(扩展)
笔记指出:InnoDB 对传统 LRU 做了优化,缓解两类问题:
- 预读失效:预读进来的页若大量不被使用,不应长期占据热区。
- 缓存池污染:如大扫描把大量「一次性」页顶到链表头部,挤出真热数据。
常见机制(与多篇《MySQL 内核》笔记一致,可作延伸阅读):
- 将 LRU 分为 New Sublist 与 Old Sublist ,新读入页插在 MidPoint(Old 区头部),而非整条 LRU 的绝对头部。
- 参数
innodb_old_blocks_pct、innodb_old_blocks_time控制冷区比例与「多久后才允许升为热区」。 - New 区内还有「前 1/4 不挪头」等实现细节,用于降低热点页链表维护开销。
若您需要单独成篇的 LRU 图解长文,可在文末「参考」中拆成第二篇发布。
九、小结
| 主题 | 要点 |
|---|---|
| 为何需要 | 弥合 CPU 与磁盘速度差,用内存缓存页 |
| 读写 | 读优先命中缓存;写先改内存页 + Redo,数据页异步刷 |
| 组成 | 数据页、索引页为主,另有 Change Buffer、AHI、锁、数据字典等 |
| 控制块 | 与缓存页 1:1,记录表空间、页号、地址、LSN 等 |
| Free 链 | 双向链表管理空闲承载页;头尾与计数控制 |
| LRU 链 | 管理已载入页;与 Free、淘汰回收配合 |
| Flush 链 | 指向 LRU 上的脏页;首次变脏入链;按最老修改 LSN 倾向优先刷 |
| 查找 | 表空间号+页号 → 哈希 → 快速判断是否命中 |
| LRU 优化 | 缓解预读失效与缓冲池污染(冷热分区 + 参数) |