InnoDB数据页结构
0.基本概念
页是InnoDB管理存储空间的基本单位,大小一般为16KB。
数据页(索引页)是用来存放表中记录的一种页。
1.数据页结构
名称 | 中文名 | 占用空间大小 | 描述 |
---|---|---|---|
File Header | 文件头部 | 38B | 页的一些通用信息 |
Page Header | 页面头部 | 56B | 数据页专有的一些信息 |
Infimun + Supremun | 最小记录和最大记录 | 26B | 两个虚拟的行记录 |
User Records | 用户记录 | 不确定 | 实际存储的行记录内容 |
Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
Page Directory | 页面目录 | 不确定 | 页中的某些记录的相对位置 |
File Tailer | 文件尾部 | 8B | 校验页是否完整 |
2.记录在页中的存储
- delete_mask:标记记录是否被删除。因为直接在磁盘上删除记录后,剩余的记录在磁盘上重新排列需要性能消耗,所以打一个标记,并且将被删除的记录组成一个垃圾链表,之后如果有新记录插入到表中时直接覆盖被删除记录占用的存储空间。
- min_rec_mask
- n_owned
- heap_no:记录在本页中的位置,0,1被自动分给两个虚拟记录,即最小记录和最大记录(比较记录的大小就是比较主键的大小)。
- record_type
- next_record:从当前记录的真实数据到下一条记录的真实数据的地址偏移量(下一条记录指的并不是插入顺序的下一条,而是主键值由小到大的顺序的下一条记录)
3.Page Directory(页目录)
沿链表顺序查找记录耗时太大,InnoDB采用目录的方式查找记录。
目录的制作过程如下:
- 将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组。
- 每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned 属性表示该记录拥有多少条记录,也就是该组内共有几条记录。
- 将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方,这个地方就是所谓的Page Directory ,也就是页目录。页面目录中的这些地址偏移量被称为槽(Slot ),所以这个页面目录就是由槽组成的。
注:最小记录所在分组只能有1条记录;
最大记录所在分组只能有1~8条分组;
剩下的分组中记录数只能为4~8条
分组步骤:
- 初始情况下一个数据页里只有最小记录和最大记录两条记录,它们分属于两个分组。
- 之后每插入一条记录,都会从页目录中找到主键值比本记录的主键值大并且差值最小的槽,然后把该槽对应的记录的n_owned 值加1,表示本组内又添加了一条记录,直到该组中的记录数等于8个。
- 在一个组中的记录数等于8个后再插入一条记录时,会将组中的记录拆分成两个组,一个组中4条记录,另一个5条记录。这个过程会在页目录中新增一个槽来记录这个新增分组中最大的那条记录的偏移量。
从页目录中查找记录步骤:
- 槽代表的记录主键从小到大排序,使用二分法快速查找该记录所在槽x(记录所在组),并找到该槽x中主键值最小的记录,因为槽x标志该组内最大记录,故找到槽x-1标志的记录,该记录的下一条记录即为槽x中主键值最小的记录。
- 通过记录的next_record 属性遍历该槽所在的组中的各个记录,直到找到目标记录。
4. Page Header(页面头部)
固定36B,专门存储页的各种状态信息。
名称 | 占用空间大小 | 描述 |
---|---|---|
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页定义 |
5.File Header(文件头部)
针对各种类型的页都通用,描述了一些针对各种页都通用的一些信息。
所有的数据页其实是一个双链表
6. File Tailer
用来检测一个页是否完整(如数据从内存同步到磁盘时发生断电,页不完整),由8B组成。
前4B代表页的校验和,这个部分是和File Header 中的校验和相对应的。每当一个页面在内存中修改了,在同步之前就要把它的校验和算出来,因为File Header 在页面的前边,所以校验和会被首先同步到磁盘,当完全写完时,校验和也会被写到页的尾部,如果完全同步成功,则页的首部和尾部的校验和应该是一致的。如果写了一半儿断电了,那么在File Header 中的校验和就代表着已经修改过的页,而在File Trailer 中的校验和代表着原先的页,二者不同则意味着同步中间出了错。
后4个字节代表页面被最后修改时对应的日志序列位置(LSN)
这个部分也是为了校验页的完整性的。