1 不同类型的页介绍
页是InnoDB管理存储空间的基本单位,一个页的大小一般是16KB。InnoDB为了不同的目的设计了多种不同类型的页,例如存放表空间的头部信息的页,存放Change Buffer信息的页、存放INODE信息的页、存放undo日志信息的页等等。
今天不准备说以上的页,而是说索引页,但是目前还是将其称之为数据页。
2 数据页结构
从上图中可以看出,一个InnoDB数据页存储空间大致被划分为7个部分,以下表格会说明一下这七个部分都存储一些什么内容
名称 | 中文名 | 简单描述 |
---|---|---|
File Header | 文件头部 | 页的一些通用信息 |
Page Header | 页面头部 | 数据页专有的一些信息 |
Infimum + Supremum | 页面中的最小记录和最大记录 | 两个虚拟的记录 |
User Records | 用户记录 | 用户存储的记录内容 |
Free Space | 空闲空间 | 页中尚未使用的空间 |
Page Directory | 页目录 | 页中某些记录的相对位置 |
File Trailer | 文件尾部 | 校验页是否完整 |
3 记录在页中的存储
在页的七个组成部分中,我们自己存储的记录会按照指定的行格式存储 User Records部分。最开始生成页的时候,并没有User Records部分,每当插入一条记录,都会从Free Space部分申请一个记录大小的空间,并将这个空间划分到User Records部分,当Free Space空间用完后,如果还有新的纪录就需要申请新的页了。
下面是记录在User Records中的一条数据记录的示例
deleted_flag:表示当前记录是否被删除,占用1比特,0表示未被删除,1表示已被删除,删除的记录不会从此版中移除掉,是因为在移除他们之后,还需要在磁盘上重新排列其他的记录,这回带来性能消耗,所有的被删除的记录会形成一个垃圾链表,记录这个链表占用的空间被称之为可重用空间,当插入新纪录时,他们可能会覆盖掉被删除的这些记录所占用的存储空间。
min_rec_flag:B+树每层非叶子节点中的最小的记录项纪录都会添加该标记。
n_owned:
heap_no:向表中插入的记录实际上是一条一条的无间排列,而一条一条的无间排列的结构称之为堆(heap)为了方便管理,每条记录在堆中的相对位置即为heap_no。而我们插入的数据的heap_no是从2开始的,0和1 则是两条伪记录,一条是页面中的最小记录(Infimum),一条是页面中的最大记录(Supremum),而记录也可以比较大小,是根据主键的大小作比较。
无论我们向表中插入了多少条记录,任何记录都比Infimum记录大,任何记录都比Supremum记录小。
recoed_type:他表示当前记录的类型,一共有4种类型,0表示普通记录,1表示B+树非页节点的目录项记录,2表示I你覅木马记录,3表示Supremum记录。
next_record:表示当前记录到吓一跳记录的距离。如果该属性值为负数,则说明当前记录的下一条记录在当前记录的前面。
无论怎样对页中的记录进行增删改操作,InnoDB始终会维护一个按照主键值从小到大的顺序形成的一个单向链表。
4 Page Directory(页目录)
为了查询时方便,InnoDB 为我们的记录制作了一个类似的目录:
----1.将所有正常的记录划分为几个组
----2.每个组的最后一条记录的头信息中的n_owned属性表示该组内共有几条记录
----3.将每组的最后一条记录在页面中的地址偏移量(也就是该记录的真实数据与页面中第0个字节之间的距离)单独提取出来,按顺序存储到靠近页尾的地方。这个地方就是Page Directory(页目录)。页目录中的这些地址偏移量成为槽,每个槽占用2字节,页目录由多个槽组成。
而给所有的记录分组是按照以下的步骤进行的:
----1.初始情况下,一个数据页只有Infimum以及Supremum记录,他们分属于两个组,所以页目录中只有两个槽。
----2.之后没插入一条记录,都会从页目录中找到相对应的记录的主键值比待插入记录的主键值大,并且差值最小的槽(从本质上说,槽是一个组内最大的那条记录在页面中的地址偏移量,通过槽可以快速找到对应的记录的主键值),然后把该槽对应的记录的n_owned值加1,表示本组内新增了一条记录,直到该组内记录数为8个。
----3.当一个组内的记录数为8后,在插入一条记录,会将组内的记录分为两个组,其中一个组4条记录,另一个组5条记录。拆分过程中会新增一个槽,记录这个新增分组中的最大记录的偏移量。
而查找数据的过程分为两步:
----1.通过二分法确定该记录所在分组对应的槽,然后找到该槽所在分组中主键值最小的那条记录。
----2.在通过记录的nexr_record属性遍历该槽所在组中的各个记录。
Page Header(页面头部)
为了能够得到存储在数据页中的记录的状态信息,比如数据页中已经存储了多少条记录、Free Space在页面中的地址偏移量、页目录中存储了多少个槽等,特意在数据页中定义了个名为Page Header的部分,它是页结构的第二部分,占用56字节,专门存储各种状态信息,具体用途如下:
状态名称 | 占用空间大小 | 描述 |
---|---|---|
PAGE_N_DIR_SLOTS | 2字节 | 在目录中的槽数量 |
PAGE_HEAP_TOP | 2字节 | 还未使用的空间的最小地址 |
PAGE_N_HEAP | 2字节 | 第一位表示本记录是否是紧凑型的记录,剩余的十五位表示本页的堆中记录的数量(包括Infimum和Supremum及被删除的记录) |
PAGE_FREE | 2字节 | 各个已删除记录通过next_record组成单向链表,这个单向链表中的记录所占用的存储空间可以被重新利用:PAGE_FREE表示该链表头节点对应记录在页面中的偏移量 |
PAGE_GARBAGE | 2字节 | 已删除记录占用的字节数 |
PAGE_LAST_INSERT | 2字节 | 最后插入记录的位置 |
PAGE_DIRECTION | 2字节 | 记录插入的方向 |
PAGE_N_DIRECTION | 2字节 | 一个方向连续插入的记录数量 |
PAGE_N_RECS | 2字节 | 该页中用户记录的数量(不包括Infimum和Supremum及被删除的记录) |
PAGE_MAX_TRX_ID | 8字节 | 最该当前页的最大事务id,该值仅在二级索引页面中定义 |
PAGE_LEVEL | 2字节 | 当前页在B+树种所处的层级 |
PAGE_INDEX_ID | 8字节 | 索引id,表示当前页属于哪个索引 |
PAGE_BTR_SEG_LEAF | 10字节 | B+树叶子节点段的头部信息,仅在B+树的根页面中定义 |
PAGE_BTR_SEG_TOP | 10字节 | B+树非叶子节点段的头部信息,仅在B+树的根页面中定义 |
5.6 File Header
该部分主要是介绍一些通用于各个页的信息,比如这个页的编号,它的上一个页和下一个页是谁等等,File Header 占用固定的38个字节
状态名称 | 占用空间大小 | 描述 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM | 4字节 | 页的校验和 |
FIL_PAGE_OFFSET | 4字节 | 页号 |
FIL_PAGE_PREV | 4字节 | 上一页的页号 |
FIL_PAGE_NEXT | 4字节 | 下一页的页号 |
FIL_PAGE_LSN | 8字节 | 页面被最后修改时的LSN值(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_SPACE_OR_CHKSUM:校验和是什么? 就是对于一个很长的字节串来说,我们会通过某种算法计算出一个比较短的值来代表这个很长的字节串,这个比较短的值就是校验和。
FIL_PAGE_OFFSET:每个页都有一个单独的页号,I你弄DB通过页号来唯一定位一个页。
FIL_PAGE_TYPE:表示当前页的类型具体类型如下所示:
类型名称 | 描述 |
---|---|
FIL_PAGE_TYPE_ALLOCATED | 最新分配还未使用 |
FIL_PAGE_UNDO_LOG | undo日志页 |
FIL_PAGE_INODE | 存储段的信息 |
FIL_PAGE_IBUF_FREE_LIST | change Buffer 空闲列表 |
FIL_PAGE_IBUF_BITMAP | change Buffer的一些属性 |
FIL_PAGE_TYPE_SYS | 存储一些系统数据 |
FIL_PAGE_TYPE_TRX_SYS | 事务系统数据 |
FIL_PAGE_TYPE_FSP_HDR | 表空间头部信息 |
FIL_PAGE_TYPE_XDES | 存储区的一些属性 |
FIL_PAGE_TYPE_BLOB | 溢出页 |
FIL_PAGE_INDEX | 索引页,也就是我们说的数据页 |
FIL_PAGE_PREV及FIL_PAGE_NEXT:I你弄DB是以页为单位存放数据,有时,数据量很大,占用空间非常大。InnoDB可能无法一次性胃饿么多数据分配一个非常大的存储空间,如果分散到多个不连续的页中进行存储,则需要把这些页关联起来,而这两个则代表本数据页的上一个页号和下一个页号,这样就可以建立起来一个双向链表,从而把数据页串联起来。
7 File Trailer(文件尾部)
InnoDB存储引擎会把数据存储到磁盘上,但是磁盘速度太慢,需要以页为单位把数据加载到内存中处理,如果该页中的数据在内存中被修改了,在修改后的某个时间还需要吧修改后的数据刷新到磁盘,如果在刷新的时候断电了,通常情况下无法判断数据页是否刷新完成,而File Trailer就是为了校验该页是否完成的。FIle Trailer分为两部分,共8个字节:
----前4个字节代表页的校验和,这个部分与FIle Header的校验和相对应,如果刷新成功,那么前后的校验和应该是一样的,如果刷新一部分断电了,那么File Header中的校验和代表着已经修改过的页,File Trailer中的校验和代表原先的页,二者不同意味着刷新期间发生了错误。
----后4个字节代表页面被最后修改时对应的LSN的后4字节,正常情况下应该与File Header部分的Fil_PAGE_LSN的后4字节相同,这个部分也是用于校验页的完整性。