NOTE
1.12 MySQL页
1. 行格式 - Record(Row、记录)在磁盘上存放的格式 1.1. Compact 1.1.1. 记录的额外信息 1.1.1.1. 变长字段长度列表 - 比如varchar(100)、blob、text,会把长度记录在这里 1.1.1.2. NULL值列表 - 使用bit记录列是否为NULL
这是历史学习笔记,可能存在过时或不完整的理解。
1. 行格式
- Record(Row、记录)在磁盘上存放的格式
1.1. Compact

1.1.1. 记录的额外信息
1.1.1.1. 变长字段长度列表
- 比如varchar(100)、blob、text,会把长度记录在这里
1.1.1.2. NULL值列表
- 使用bit记录列是否为NULL。列为NULL则为1,否则为0
1.1.1.3. 记录头信息
| 名称 | 占用bit | 描述 |
|---|---|---|
| 预留位1 | 1 | 没有使用 |
| 预留位2 | 1 | 没有使用 |
| delete_mask | 1 | 标记该记录是否被删除 |
| min_rec_mask | 1 | B+树的每层非叶子节点中的最小记录都会添加该标记 |
| n_owned | 4 | 表示当前记录拥有的记录数 |
| heap_no | 13 | 表示当前记录在记录堆的位置信息 |
| record_type | 3 | 表示当前记录的类型, 0 表示普通记录, 1 表示B+树非叶子节点记录, 2 表示最小记录, 3表示最大记录 |
| next_record | 16 | 表示下一条记录的相对位置 |
1.1.1.3.1. delete_mask
- 被删除的记录不会真的物理删除,而是逻辑删除
- 所有被删除掉的记录都会组成一个所谓的 垃圾链表 ,在这个链表中的记录占用的空间称之为所谓的 可重用空间 ,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉
- 这也就意味着删除之后不会释放空间
- 可以使用
optimize table table_name释放磁盘空间
- 可以使用
1.1.1.3.2. min_rec_mask
- B+树的每层非叶子节点中的最小记录都会添加该标记
1.1.1.3.3. next_record
- 从当前记录的真实数据到下一条记录的真实数据的地址偏移量。说白了我们的记录按照主键从小到大的顺序形成了一个单链表
1.1.1.3.4. heap_no
- 表示当前记录在本页中的位置。比如2、3、4、5。至于0和1则是最小记录和最大记录
1.1.1.3.5. record_type
- 表示当前记录的类型,一共有4种类型的记录, 0 表示普通记录, 1 表示B+树非叶节点记录, 2 表示最小记录, 3 表示最大记录
1.1.2. 记录的真实数据
- row_id
- transaction_id
- roll_pointer
- 列1的值
- 列2的值
- …
1.2. Redundant
- MySQL5.0 之前用的一种行格式
1.3. Dynamic
- MySQL5.7的默认格式
- 与Compact一样,区别在于处理行溢出的数据
- 不会在记录的真实数据处存储字段真实数据的前 768 个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地方
- 行溢出
- 一个页面能存储16KB的数据,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为行溢出
1.4. Compressed
与Dynamic一样,区别在于会采用压缩算法对页面进行压缩
2. 页
- InnoDB或MyISAM管理存储空间的基本单位,一个页的大小一般是 16KB
2.1. 数据页
| 名称 | 中文名 | 占用空间大小 | 简单描述 |
|---|---|---|---|
| File Header | 文件头部 | 38 字节 | 页的一些通用信息 |
| Page Header | 页面头部 | 56 字节 | 数据页专有的一些信息 |
| Infimum + Supremum | 最小记录和最大记录 | 26 字节 | 两个虚拟的行记录 |
| User Records | 用户记录 | 不确定 | 实际存储的行记录内容 |
| Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
| Page Directory | 页面目录 | 不确定 | 页中的某些记录的相对位置 |
| File Trailer | 文件尾部 | 8 字节 | 校验页是否完整 |
2.1.1. File Header
- 所有类型的页通用
| 名称 | 占用空间大小(Byte) | 简单描述 |
|---|---|---|
| FIL_PAGE_SPACE_OR_CHKSUM | 4 | 页的校验和(checksum值) |
| FIL_PAGE_OFFSET | 4 | 页号 |
| FIL_PAGE_PREV | 4 | 上一个页的页号 |
| FIL_PAGE_NEXT | 4 | 下一个页的页号 |
| FIL_PAGE_LSN | 8 | 页面被最后修改时对应的日志序列位置(英文名是:Log Sequence Number) |
| FIL_PAGE_TYPE | 2 | 该页的类型 |
| FIL_PAGE_FILE_FLUSH_LSN | 8 | 仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值 |
| FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 | 页属于哪个表空间 |
- FIL_PAGE_PREV 和 FIL_PAGE_NEXT:
- 只有INDEX类型的页,也就是数据页才有这两个
- 分别代表本页的上一个和下一个页的页号
- 使用双向链表把多个页串起来

- FIL_PAGE_OFFSET:
- 每一个 页 都有一个单独的页号
- 由4Bytes组成,也就是说表空间可以存放
2^32个页,每个页16KB,那么一个表空间最大64TB
- FIL_PAGE_TYPE:
- 当前 页 的类型
- 比如存放记录的数据页的类型其实是 FIL_PAGE_INDEX ,也就是所谓的 索引页
2.1.2. Page Header
- 数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等
| 名称 | 占用空间大小(Byte) | 简单描述 |
|---|---|---|
| PAGE_N_DIR_SLOTS | 2 | 在页目录中的槽数量 |
| PAGE_HEAP_TOP | 2 | 还未使用的空间最小地址,也就是说从该地址之后就是 Free Space |
| PAGE_N_HEAP | 2 | 本页中的记录的数量(包括最小和最大记录以及标记为删除的记录) |
| PAGE_FREE | 2 | 第一个已经标记为删除的记录地址(各个已删除的记录通过 next_record 也会组成一个单链表,这个单链表中的记录可以被重新利用) |
| PAGE_GARBAGE | 2 | 已删除记录占用的字节数 |
| PAGE_LAST_INSERT | 2 | 最后插入记录的位置 |
| PAGE_DIRECTION | 2 | 记录插入的方向 |
| PAGE_N_DIRECTION | 2 | 一个方向连续插入的记录数量 |
| PAGE_N_RECS | 2 | 该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录) |
| PAGE_MAX_TRX_ID | 8 | 修改当前页的最大事务ID,该值仅在二级索引中定义 |
| PAGE_LEVEL | 2 | 当前页在B+树中所处的层级 |
| PAGE_INDEX_ID | 8 | 索引ID,表示当前页属于哪个索引 |
| PAGE_BTR_SEG_LEAF | 10 | B+树叶子段的头部信息,仅在B+树的Root页定义 |
| PAGE_BTR_SEG_TOP | 10 | B+树非叶子段的头部信息,仅在B+树的Root页定义 |
- PAGE_DIRECTION:新插入的一条记录的主键值比上一条记录的主键值大,我们说这条记录的插入方向是右边,反之则是左边
- PAGE_N_DIRECTION:假设连续几次插入新记录的方向都是一致的, InnoDB 会把沿着同一个方向插入记录的条数记下来,这个条数就用 PAGE_N_DIRECTION 这个状态表示
- PAGE_N_DIR_SLOTS:在页目录中的槽数量
- PAGE_LAST_INSERT:最后插入记录的位置
- PAGE_N_RECS:该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录)
2.1.3. User Records
- 实际存储的行记录内容
- User Records增长时Free Space会缩小
2.1.4. Free Space
- 页中尚未使用的空间
- User Records增长时Free Space会缩小
2.1.5. Page Directory
2.1.5.1. 为什么有Page Directory
- 记录在页中按照主键值由小到大顺序串联成一个单链表,那如果我们想根据主键值查找页中的某条记录需要遍历链表查找,这个效率太慢了
2.1.5.2. Page Directory是什么
- 将记录分组,每组的最后一条记录的地址存放在页目录中,叫做槽。其实就是目录
- 在一个数据页中查找指定主键值的记录的过程分为两步
- 通过二分法确定该记录所在的槽,并找到该槽中主键值最小的那条记录
- 通过记录的 next_record 属性遍历该槽所在的组中的各个记录
2.1.6. File Trailer
2.1.6.1. 为什么有File Trailer
- InnoDB以页为单位把磁盘数据加载到内存中处理,如果该页的数据在内存中修改了,需要把一段时间后把整页写回磁盘。但是如果在写回的过程中宕机了,那么数据就不正确了,于是为了检测一个页是否完整,在每个页的尾部都加了一个 File Trailer
2.1.6.2. File Trailer是什么
- 所有类型的页通用
2.2. 索引页
2.2.1. 为什么需要索引页
Page Directory只能用于页内的主键进行快速查找,如果是非主键或者跨多个页就不行了
2.2.2. 索引页和数据页的关系
- 也是用页存储,只不过User Record的record_type为1,表示目录项(即索引)
- 一层目录:

- 多层目录:


