mysql05——数据页
InnoDB数据页结构
数据页默认大小是16kb,而数据页之间是以双向链表的形式存储,数据页里的记录以单链表的形式连接。
MySQL的数据页类型有十多种,包括:索引页,Undo页,Inode页,系统页,Blob页等。但我们最常用的就是索引页,所以我们就针对该页来进行分析。接下来所说的数据页都指代索引页。
数据页结构
数据页代表的这块16kb大小的存储空间可以被划分为7个部分,有的部分占用的字节数是确定的,有的不确定的。具体描述如下表:
名称 | 中文名 | 占用空间 | 简单表述 |
---|---|---|---|
File Header | 文件头部 | 38Byte | 页的一些通用信息 |
Page Header | 页面头部 | 56Byte | 数据页专有的一些信息 |
Infinum + Supremum | 最小记录和最大记录 | 26Byte | 两个虚拟的行记录 |
User Records | 用户记录 | 不确定 | 实际的行记录内容 |
Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
Page Directory | 页面目录 | 不确定 | 也中的某些记录的相对位置 |
File Trailer | 文件尾部 | 8Byte | 校验页是否完整 |
- 一开始生成页的时候是没有
User Records
的,每当插入一条记录都会从Free Space
中申请一个记录大小的空间到User Records
。 - 当
Free Space
部分的空间全部被User Records
部分替代掉后,就意味着这个页使用完了,如果后面还有新的记录插入,就需要申请新的页。
记录头信息详解
如果创建的时候指定了主键,具体的行格式中InnoDB就不会创建db_row_id
隐藏列。
- delete_mask:表示当前记录是否被删除,占用一个二进制位,值为0的时候代表记录并没有被删除,为1代表已经被删除。
- 因为移除重新排列需要消耗性能,这个地方只打标,所有删除掉的记录会组成一个所谓的垃圾列表。 在这个链表中的记录占用的空间称之为可重用空间。
- min_rec_mask:
- n_owned:
- heap_no:这个属性表示当前记录在本页中的位置,如果插入的4条记录在本页中的位置分别是:2、3、4、5。比较记录的大小就是比较主键的大小。
- 而每个innodb页的heap_no=0表示最小记录(5byte)、1表示最大记录(8byte)。
- record_type:这个属性表示当前的记录类型。
- 0:表示普通记录。
- 1:B+树非叶节点激励。
- 2:最小记录。
- 3:最大记录。
- next_record:从当前记录的真实数据到下一条记录的真实数据的地址偏移量。
- 下一条记录指得并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。
- Infimum记录的下一条就是本页中主键值最小的user record,而本页中主键值最大的用户记录的下一条就是Supremum记录。
当删除了第二条记录会产生如下变化:
- 第二条记录并没有从存储空间移除,而是把该条记录的delete_mask设置为1
- 第二条记录的next_record变为了0,表示没有下一条记录。
- 第一条记录的next_record指向了第三条。
- 最大记录的n_owned值从5变成了4。
无论怎么对页中的记录做增删改查,Innodb使用会维护一条记录的单向链表,链表中的各个节点是按照主键值有小到大的顺序连接起来的。
Page Directory(页目录)
InnoDB制作了目录,制作过程如下:
- 将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组。
- 每个组的最后一条记录(也就是组内最大记录)的头信息中的
n_owned
属性表示该记录拥有多少条记录,也就是该组共有几条记录。 - 将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方,这个地方就是
Page Directory
,也就是页目录。页面目录中的这些地址偏移量本称为槽(slot)。
头信息的n_owned属性:
- 最小记录的
n_owned
值为1,这就代表以最小记录结尾的这个分组中只有一条记录,也就是最小记录本身。 - 最大记录的
n_owned
值为5,这就代表着最大记录结尾的这个分组中只有5条记录,包括最大记录本身还有我们自己插入的4条记录。 - 每个分组中的记录条数规定是,对于最小记录所在的分组只能有1条记录,最大记录所在的分组拥有的记录条数只能在18条质检,剩下的分组中记录的条数范围只能是在48条之间。
- 初识情况下一个数据页里只有最小记录和最大记录两条记录,它们分属于两个分组。
- 之后每插入一条记录,都会从页目录中找到主键值比本记录的主键值大并且差值最小的槽,然后把该槽对应的记录的
n_owned
值加1,(这个槽可以是最小值和最大值,也可以是本组最后一条记录)。 - 在一个组中的记录数等于8之后,在插入一条会将组中的记录拆分为两个组,一个组中4条记录,另外一条5条。这个过程会在页目录中新增一个槽来记录这个新增分组中最大的那条记录的偏移值。
二分查找:
假设一共16条信息,外加最大最小记录一共18条记录。槽0是最小记录,槽1~槽3分别包含4条记录为一组。槽5有5条记录,包括最大记录。
如果查询id为5的记录:
- 首先查询槽 (0 + 4 )/2 = 2,槽2的记录为id=8;
- 查询槽(0 + 2)/2=1,槽1的记录为4;
- 因为high - low = 1,所以确定主键值为5的记录在槽2在中。然后遍历槽2对应的
所以在一个数据页中查找指定主键值的记录的过程分为两步:
- 通过二分法确定该记录所在的槽。
- 通过记录的
next_record
属性遍历该槽所在的组中的各个记录。
Page Header(页面头部)
Page Header是专用针对数据页记录的各种状态信息。
File Header(文件头部)
File Header针对各种类型的也都通用,它描述了一些针对各种也都通用的一些信息。比较重要的部分:
- FIL_PAGE_SPACE_OR_CHKSUM: 这个代表当前页面的校验和(checksum)。
- FIL_PAGE_OFFSET: 每一个页都会有一个单独的页号,就跟你的身份证号码一样,InnoDB通过页号可以唯一定位一个页。
- FIL_PAGE_TYPE: 代表当前也的类型。
- FIL_PAGE_PREV和FIL_PAGE_NEXT: Innodb都是以页为单位存放数据的,通过建立双向链表把许许多多的页就都串联起来了。并不是所有页都有这个字段。
File Trailer
为了防止把数据同步到磁盘过程中出现断点等情况,为了检测一个页是否完整,设计了一个File Trailer部分。这个部分可以分为两个小部分:
- 前4个字节代表页的校验和。
- 后4个字节代表页面被最后修改时对应的日志序列位置(LSN)