文章目录
-
- [《MySQL 页结构与数据存储原理全解析》](#《MySQL 页结构与数据存储原理全解析》)
-
- [一、前言:理解页结构,是深入 InnoDB 的起点](#一、前言:理解页结构,是深入 InnoDB 的起点)
- 二、页的定义与基本单位
- 三、页的内部结构详解
-
- [(1)File Header(文件头)](#(1)File Header(文件头))
- [(2)Page Header(页头)](#(2)Page Header(页头))
- [(3)Infimum/Supremum Record](#(3)Infimum/Supremum Record)
- [(4)User Records(用户记录)](#(4)User Records(用户记录))
- [(5)Free Space(空闲空间)](#(5)Free Space(空闲空间))
- [(6)Page Directory(页目录)](#(6)Page Directory(页目录))
- 四、数据行在页中的存储方式
- 五、索引页与数据页的关系
- 六、页分裂与合并
- 七、面试高频问题与答题模板
- [八、总结:理解页,就是理解 InnoDB](#八、总结:理解页,就是理解 InnoDB)
《MySQL 页结构与数据存储原理全解析》
一、前言:理解页结构,是深入 InnoDB 的起点
大家好,我是程序员卷卷。
我们平时写 SQL 时,看到的都是逻辑上的"表""行""列",
但在 InnoDB 的底层,这一切都以 页(Page) 为单位进行物理存储与管理。
无论是索引查找、缓冲缓存、磁盘读写,最小单位都是"页"。
这意味着:
如果你真正理解了页的结构,就能从底层解释"为什么随机 IO 慢、为什么自增主键快、为什么行锁粒度能控制到单行"。
二、页的定义与基本单位
在 InnoDB 中:
- 页(Page)是磁盘与内存交互的最小单位;
- 每页默认大小为 16KB;
- 多个页组成区(Extent,1MB),多个区组成段(Segment),段组成表空间(Tablespace)。
层级结构如下:
表空间(Tablespace)
├── 段(Segment)
│ └── 区(Extent)
│ └── 页(Page, 16KB)
│ └── 行(Row)
简单理解:
InnoDB 把磁盘空间分成了一个个"16KB 的小格子",每个格子就是一个 Page。
三、页的内部结构详解
每个页内部都包含固定的逻辑结构,可分为以下部分
┌───────────────────────────┐
│ File Header(文件头) │ 38字节,存页号、校验值等
│ Page Header(页头) │ 56字节,页目录、行数等
│ Infimum/Supremum Record │ 固定两条虚拟记录
│ User Records(用户记录) │ 存储真实数据行
│ Free Space(空闲空间) │ 插入新行时使用
│ Page Directory(页目录) │ 记录偏移地址,加快查找
│ File Trailer(校验信息) │ 校验页完整性
└───────────────────────────┘
(1)File Header(文件头)
记录页的编号、上一个页、下一个页、校验值,用于维护 B+Tree 的双向链表结构。
(2)Page Header(页头)
存储当前页中记录数量、最小和最大记录位置等元信息。
(3)Infimum/Supremum Record
两条虚拟记录,分别代表"最小值"和"最大值",
保证页内记录有序,便于二分查找。
(4)User Records(用户记录)
实际存储表中的数据行。
InnoDB 会根据主键顺序在页内排序,形成 B+Tree 的叶子节点结构。
(5)Free Space(空闲空间)
用于存放后续新增的行,当空间不足时会触发页分裂。
(6)Page Directory(页目录)
存储每条记录的偏移地址,用于加速定位。
查找记录时,InnoDB 先通过目录定位,再顺序扫描。
四、数据行在页中的存储方式
每一行数据在页中以"记录(Record)"形式存在,
每条记录包含两部分:记录头信息 + 实际列值。
┌───────────────────────────────┐
│ Record Header(记录头,5字节)│
│ + delete_flag │
│ + next_record 指针 │
│ + record_type │
│ Column Values(真实列数据) │
└───────────────────────────────┘
举例说明
表结构:
sql
CREATE TABLE user (
id INT PRIMARY KEY,
name VARCHAR(20),
age INT
) ENGINE=InnoDB;
在页中表现为:
- 页内记录根据 主键 id 有序排列;
- 每条记录包含一个 "next_record" 指针,形成单向链表;
- 通过页目录(Page Directory)加速二分查找;
- 若页满,页会分裂(Page Split),形成新的页并更新 B+Tree 链接。
五、索引页与数据页的关系
InnoDB 的 B+Tree 索引中:
- 叶子节点页 存放实际数据行(称为"数据页");
- 非叶子节点页 存放索引键与指针(称为"索引页")。
示意图如下
┌─────────────────────┐
│ 根节点页 │
└─────────┬───────────┘
┌─────────┴───────────┐
┌────────▼────────┐ ┌────────▼────────┐
│ 索引页(中间层) │ │ 索引页(中间层) │
└────────┬────────┘ └────────┬────────┘
┌───────▼───────┐ ┌───────▼───────┐
│ 数据页(叶子层) │ │ 数据页(叶子层) │
└────────────────┘ └────────────────┘
所以 InnoDB 的主键索引即"聚簇索引",
数据行就存放在 B+Tree 的叶子节点中。
六、页分裂与合并
当插入新数据导致页满时:
- InnoDB 会分裂出新页;
- 重新分配部分记录;
- 更新父节点指针。
页合并发生在删除大量数据时,
若相邻页利用率过低,会自动合并,减少碎片。
这两种操作是维护 B+Tree 平衡结构的关键。
七、面试高频问题与答题模板
| 问题 | 答案要点 |
|---|---|
| Q1:InnoDB 一页多大?为什么是 16KB? | 默认 16KB,兼顾 I/O 性能与内存命中率。 |
| Q2:为什么 InnoDB 查找时要"回表"? | 因为二级索引页存储主键值,需要到数据页(聚簇索引)获取完整行数据。 |
| Q3:页目录的作用是什么? | 存储记录偏移,加快页内定位。 |
| Q4:页分裂是什么? | 页空间不足时,数据部分迁移到新页,更新索引指针。 |
| Q5:InnoDB 的叶子页与非叶子页区别? | 叶子页存数据行,非叶子页存索引键与页号指针。 |
| Q6:页是如何在内存与磁盘之间交互的? | InnoDB Buffer Pool 以页为单位进行读写与缓存。 |
八、总结:理解页,就是理解 InnoDB
MySQL 的性能优化,本质上就是对"页"的利用优化。
- 索引查找靠页定位;
- Buffer Pool 按页缓存;
- 磁盘 I/O 按页读写;
- 表结构设计决定页利用率。
理解页结构,就能从物理层回答:
"为什么自增主键比随机主键更快?"
------因为自增主键插入不会频繁触发页分裂。
下一篇(第 4 篇),我将写------
《CHAR、VARCHAR、TEXT 的差别与存储方式》 ,
带你理解字符串类型的底层存储差异、长度限制与性能影响。