Mysql(二十二)——InnoDB页结构
这个就是InnoDB的页结构,我们上次说过不管是数据页还是目录页都是一个页,因为InnoDB数据的本质都是由一个一个16K的页组成的。
文件头
我们先来说一下文件头,这个就是描述一个页的信息的,也就相当于身份证一样,这个偏移量表示这个字段在这个内存块中的什么位置,可以理解为页就是一个内存块,当然不一定是连续的,如果这个偏移量就是指针的偏移量,可以理解为起始地址+-偏移量=字段地址的意思。
偏移量 | 字节数 | 字段名 | 十六进制表示 | 描述 | 重要性 |
---|---|---|---|---|---|
0 | 4 |
| 0x00000000~0xFFFFFFFF | 页的校验和或表空间ID | ⭐⭐⭐⭐ (崩溃恢复关键) |
4 | 4 |
| 0x00000000~0xFFFFFFFF | 页号(在表空间内的偏移量) | ⭐⭐⭐⭐⭐ (唯一标识页) |
8 | 4 |
| 0x00000000~0xFFFFFFFF | 上一页的页号(B+树特性) | ⭐⭐⭐ (索引页重要) |
12 | 4 |
| 0x00000000~0xFFFFFFFF | 下一页的页号(B+树特性) | ⭐⭐⭐ (索引页重要) |
16 | 4 |
| 0x00000000~0xFFFFFFFF | 该页最后被修改的LSN(日志序列号) | ⭐⭐⭐⭐⭐ (ACID保障) |
20 | 2 |
| 0x0000~0xFFFF | 页类型(见下方类型表) | ⭐⭐⭐⭐ (决定页用途) |
22 | 4 |
| 0x00000000~0xFFFFFFFF | 仅文件的第一页有效,表示文件刷新LSN | ⭐ (系统页专用) |
26 | 4 |
| 0x00000000~0xFFFFFFFF | 表空间ID(MySQL 5.7+) | ⭐⭐⭐ (空间管理) |
30 | 8 | 保留字段 | - | 未来扩展使用 | - |
这个是页类型的表格:
类型值(十六进制) | 常量名 | 说明 |
---|---|---|
0x0000 |
| 新分配的页 |
0x0001 |
| 系统页 |
0x0002 |
| Undo日志页 |
0x0003 |
| 索引节点页 |
0x0004 |
| Insert Buffer空闲列表页 |
0x45BF |
| B+树索引页(数据页) |
0x0006 |
| 事务系统页 |
0x0007 |
| 表空间头页 |
0x0008 |
| 扩展描述页 |
我们要做的是文件之间的链式关系是存在文件头中的,页的类型也是在文件中的,文件的页ID(页号)和文件校验码和最后一次修改日志序列号在文件头中就可以了。
页头
偏移量 | 字节数 | 字段名 | 取值范围 | 描述 | 重要性 |
---|---|---|---|---|---|
38 | 2 |
| 0~65535 | 页目录中的槽位数 | ⭐⭐⭐⭐ (二分查找基础) |
40 | 2 |
| 0~16384 | 堆中空闲空间的起始偏移 | ⭐⭐⭐⭐ (空间管理) |
42 | 2 |
| 0~65535 | 堆中的记录数(包括Infimum/Supremum和已删除记录) | ⭐⭐⭐ (记录统计) |
44 | 2 |
| 0~16384 | 指向空闲链表头部的指针 | ⭐⭐⭐ (空间重用) |
46 | 2 |
| 0~65535 | 已删除记录的字节总数 | ⭐⭐ (空间碎片) |
48 | 2 |
| 0~16384 | 最后插入记录的位置 | ⭐⭐ (插入优化) |
50 | 2 |
| 0~65535 | 最后插入的方向(0=未知,1=左,2=右) | ⭐ (插入模式) |
52 | 2 |
| 0~65535 | 同一方向连续插入的次数 | ⭐ (插入优化) |
54 | 2 |
| 0~65535 | 页中用户记录数(不包括系统记录和已删除记录) | ⭐⭐⭐ (关键统计) |
56 | 8 |
| 0~2⁶⁴-1 | 修改该页的最大事务ID(MVCC用) | ⭐⭐⭐⭐ (事务隔离) |
64 | 2 |
| 0~65535 | 页在B+树中的层级(0表示叶子页) | ⭐⭐⭐⭐⭐ (索引结构) |
66 | 8 |
| 0~2⁶⁴-1 | 索引ID(属于哪个索引) | ⭐⭐⭐ (索引管理) |
74 | 2 |
| - | B+树叶子节点段的头部信息 | ⭐⭐ (存储引擎内部) |
76 | 2 |
| - | B+树非叶子节点段的头部信息 | ⭐⭐ (存储引擎内部) |
78 | 10 | 保留字段 | - | 未来扩展使用 |
这个页头是索引页的页头,因为不同类型的页他对应的页头也是不同的,这边就不介绍其他也类型的页头了。就单论索引页的页头来说,我们需要记住的是:
1.记录了当前的索引id、索引的层级,槽位数量;
2.记录了空闲链表的指针、最大记录数之和(包括删除的)、最小记录数之和(不包括删除的)、最后修改的事务id(也就是最大事务id);
3.插入信息(左插入还是右插入,同一个方向最大的插入数)。
Infimum+Supremum
这个就没什么好说的,他们是页的左右边界,告诉我们用户记录最左边的一条地址,和用户记录最右边一条的地址。
用户记录和空闲记录
用户记录
字段 | 字节数 | 描述 | 可变性 | 重要性 |
---|---|---|---|---|
记录头(Record Header) | 5字节 | 包含删除标记、记录类型等信息 | 固定 | ⭐⭐⭐⭐⭐ |
事务ID(DB_TRX_ID) | 6字节 | 最后修改该记录的事务ID | 固定 | ⭐⭐⭐⭐ (MVCC) |
回滚指针(DB_ROLL_PTR) | 7字节 | 指向undo日志记录的指针 | 固定 | ⭐⭐⭐⭐ (MVCC) |
列1数据 | 变长 | 第一列实际数据 | 可变 | ⭐⭐⭐ |
列2数据 | 变长 | 第二列实际数据 | 可变 | ⭐⭐⭐ |
... | ... | ... | ... | ... |
列N数据 | 变长 | 最后一列实际数据 | 可变 | ⭐⭐⭐ |
下条记录指针 | 1-4字节 | 指向页内下一条记录的偏移量 | 可变 | ⭐⭐⭐⭐ |
这个是没一条数据的结构,很简单,我们只要记住是存一条一条记录的就可以了,记录头告诉我们当前数据是否被标记为脏页,是删除还是修改,事务id就是那一次事务操作了这个数据,回滚指针对于的就是如果要回滚应该回归到什么版本。
空闲记录
字段 | 字节数 | 描述 |
---|---|---|
next_free | 4字节 | 指向下一个空闲记录的指针 |
original_length | 2字节 | 原始记录的总长度 |
original_header | 5字节 | 原始记录头副本 |
剩余空间 | 可变(看被删记录空间多大) | 可被新记录重用的空间 |
这个其实也很好理解,如果我们删除了一个数据,这个数据并不会被真的删除,而是在用户记录的记录头中标记为删除,然后sql查询不可见这条纪律,然后这个被删除的记录会被空闲记录上一条记录的next_free指向,然后记录长度和原记录头信息,然后下次插入一条数据的时候,会先在页头中查找空闲链表,没有空闲链表了就好使用空闲链表中的空间,把原来被标记删除的数据覆盖掉。
页目录
组成部分 | 大小 | 描述 | 作用 |
---|---|---|---|
槽位数组(Slot Array) | 每槽2字节 | 存储指向记录的偏移量 | 实现二分查找 |
槽位数量 | 2字节 | 存储在页头的 | 记录当前槽位数 |
槽位间隔 | 动态 | 通常每4-8条记录一个槽位 | 平衡查找效率与空间开销 |
这个就是我们一直在说的槽位,就是在页目录中,具体是怎么用的我这边就不锁了,当时讲InnoDB的索引结构的时候锁的很清楚了(主包第19篇)。
文件尾
字段名 | 字节偏移 | 大小 | 数据类型 | 描述 | 重要性 |
---|---|---|---|---|---|
FIL_PAGE_END_LSN | 16376 | 8字节 | uint64 | 页的日志序列号(LSN)校验值 | ⭐⭐⭐⭐⭐ |
文件尾是InnoDB页结构的最后一个组成部分,位于每个16KB页的最后8字节。它主要用于崩溃恢复和数据完整性验证。
这个是文件头和文件尾记录的LSN的区别:
特性 | 文件头LSN (FIL_PAGE_LSN) | 文件尾LSN (FIL_PAGE_END_LSN) |
---|---|---|
位置 | 页的第16-23字节(文件头部分) | 页的最后8字节(文件尾部分) |
写入时机 | 页修改后立即更新 | 页写入磁盘前最后写入 |
作用 | 记录页的最新修改版本 | 验证页写入的完整性 |
崩溃恢复角色 | 判断是否需要redo | 检测部分页写入(partial write) |
更新频率 | 每次页修改都更新 | 仅在持久化到磁盘时更新 |
校验对象 | 页数据的逻辑一致性 | 页写入的物理完整性 |
内存中状态 | 常驻内存保持最新 | 仅在写入磁盘时生成 |
一句话的意思就是,文件头的LSN是页的"版本号",动态反映页的最新状态,决定redo日志的应用范围,而文件尾的LSN是页的"校验码",仅在持久化时生成,确保页完整写入磁盘。
总结
本篇主要讲了Mysql中InnoDB的页的结构。