数据页和索引页
核心概念:什么是 Buffer Pool?
重温一下 Buffer Pool(缓冲池):
它是 InnoDB 存储引擎中最大的一块内存区域,用来缓存表和索引数据。因为从内存访问数据比从磁盘访问要快几个数量级,所以 Buffer Pool 对数据库性能至关重要。当需要访问数据时,InnoDB 会先尝试从 Buffer Pool 中读取。如果找不到(这称为“缺页”),才会从磁盘读取相应的页并加载到 Buffer Pool 中。
页(Page) 是 InnoDB 磁盘管理的最小单位,默认大小为 16KB。Buffer Pool 中存储的就是这些从磁盘加载上来的页的副本。
数据页(Data Pages) vs 索引页(Index Pages)
虽然它们在 Buffer Pool 中都是“页”,但其内容和用途有本质区别。
1. 数据页(Data Pages / Leaf Pages of the Clustered Index)
- 存储内容:表的实际行数据(表中的每一行记录)。
- 本质:在 InnoDB 中,表就是根据主键顺序组织的一个聚集索引(Clustered Index)。而这个聚集索引的叶子节点(Leaf Pages) 存储的就是完整的数据行。因此,数据页就是聚集索引的叶子节点。
- 包含信息:
- 除了用户定义的列数据,还包括一些内部字段,如:
Transaction ID:最近修改该行的事务ID。Roll Pointer:指向该行上一个版本的 undo log 记录,用于实现 MVCC(多版本并发控制)。
- 行数据以一种高效的格式(如 Compact、Redundant、Dynamic 等行格式)存储。
- 除了用户定义的列数据,还包括一些内部字段,如:
- 访问方式:最终要查询的实际数据都在这里。无论是通过主键查询,还是通过二级索引查询,最终都需要访问到数据页来获取完整的行数据(除非查询可以完全被索引覆盖)。
2. 索引页(Index Pages)
索引页主要分为两种:
A. 聚集索引的非叶子节点页(Non-Leaf Pages of the Clustered Index)
- 存储内容:索引键值(主键值) + 指针(子节点的页号)。
- 作用:构成 B+Tree 的枝干,用于快速定位数据所在的数据页(叶子节点)。它们就像一本书的目录,告诉你你要找的内容在哪一页。
B. 二级索引页(Secondary Index Pages)
- 存储内容:
- 叶子节点:存储的是二级索引的键值 + 对应记录的主键值。它不存储完整的行数据。
- 非叶子节点:同样是索引键值 + 指针(子节点的页号)。
- 作用:提供另一种快速查找数据路径。例如,你在
username字段上建立了索引,通过username='john'查询时,会先遍历二级索引树,找到对应的主键 ID,然后再用这个主键 ID 去聚集索引中查找完整的数据行(这个过程称为“回表”)。
区别对比总结
| 特性 | 数据页 (Data Pages) | 索引页 (Index Pages) |
|---|---|---|
| 存储内容 | 完整的行数据(聚集索引的叶子节点) | 索引键值 + 指针(或 索引键值 + 主键) |
| 本质 | 就是表本身 | 是数据的目录或捷径 |
| 对应索引 | 聚集索引(Clustered Index)的叶子节点 | 1. 聚集索引的非叶子节点 2. 整个二级索引(包括其非叶子节点和叶子节点) |
| 作用 | 存储数据的最终目的地 | 快速定位数据的位置 |
| 访问路径 | 任何查询的最终目标(除非索引覆盖) | 查询的路径,用于加速查找 |
| 大小影响 | 受表行数和行大小影响 | 受索引数量和索引键大小影响 |
它们在 Buffer Pool 中的交互
一次典型的查询(如 SELECT * FROM users WHERE username = 'alice')在 Buffer Pool 中的流程完美展示了两者的协作:
- 确认
username有二级索引:InnoDB 会识别出可以使用username上的索引。 - 加载索引页:
- 从磁盘将相关二级索引的根页、非叶子节点页加载到 Buffer Pool(如果尚未存在)。
- 沿着二级索引的 B+Tree 查找,直到在叶子节点中找到
username='alice'的条目,该条目包含了对应的主键值(例如id=123)。
- 回表(Bookmark Lookup):
- 现在有了主键
id=123,接着去聚集索引中查找。 - 从磁盘将相关聚集索引的根页、非叶子节点页加载到 Buffer Pool(如果尚未存在)。
- 沿着聚集索引的 B+Tree 查找主键
id=123。
- 现在有了主键
- 加载数据页:
- 最终定位到聚集索引的某个数据页(叶子节点),该页包含了
id=123的完整行数据。 - 如果这个数据页不在 Buffer Pool 中,则从磁盘加载它。
- 最终定位到聚集索引的某个数据页(叶子节点),该页包含了
- 返回数据:从 Buffer Pool 中的数据页中读取该行记录并返回给客户端。
实践意义与监控
- 内存分配:Buffer Pool 的大小决定了你能缓存多少数据页和索引页。如果 Buffer Pool 太小,无法容纳常用数据和索引,就会导致大量的磁盘 I/O,性能急剧下降。
- 预热(Warm-up):数据库重启后,Buffer Pool 是空的。需要经过一段时间的查询,才能将热点的数据页和索引页加载进来,这段时间性能较差。你可以通过监控
SHOW ENGINE INNODB STATUS\G中的Pages read ahead和evicted without access等指标来观察。 - LRU 算法:Buffer Pool 使用 LRU(最近最少使用)算法的变体来管理这些页。无论是数据页还是索引页,如果最近被访问过,就会排在 LRU 列表的前端,避免被淘汰出内存。长时间未被访问的页会被移动到尾部,当需要空间加载新页时,尾部的页就会被驱逐(evict)。
总结:你可以将 Buffer Pool 想象成一个繁忙图书馆的热门图书展示区。索引页就像是图书的索引卡片,告诉你某本书在哪个书架上。数据页就是书本本身的内容。Buffer Pool 会同时把最常被查阅的索引卡片和最常被阅读的书籍本身放在这个展示区,让你无需每次都跑去遥远的大书库(磁盘)查找,从而极大提高效率。

浙公网安备 33010602011771号