InnoDB存放着各种各样的页(page),这些page也分为各种不同的类型。如:FIL_PAGE_INDEX数据页(带有真实数据的)、FIL_PAGE_TYPE_ALLOCATED未分配页、FIL_PAGE_TYPE_BLOG溢出页……等十多个类型的页,用于不同的用途。
补充:页面管理的分类:
-
空闲页:空闲页
-
数据页:干净页
-
脏页:跟磁盘数据不一致,需要生效
续上:
我们最首先要了解的应该是数据页——FIL_PAGE_INDEX,因为这里存放着我们索引和真实的数据。
数据页的结构大致分为7个部分,有些部份是固定大小的,有些部份是不固定的
-
File Header:文件头部,38字节
-
Page Header:页面头部,56字节
-
Infimum+Superman:页面中的最小记录+最大记录。是虚拟记录
-
User Records:用户记录,真实数据
-
Free Page:空闲空间,尚未使用的空间
-
Page Directory:页目录
-
File Trailer:文件尾
Page Header:数据页中的记录的状态信息,如记录的数量,空闲空间相关信息、槽的数量等
File Header:存放页的通用信息,所有类型的页都有这个。如页的编号、上下页的地址
File Trailer:4字节的校验和+4字节的LSN后4字节。均做校验使用,在数据修改后写入到磁盘前计算出来,在写入磁盘后核对一遍,校验和不一致则写入期间发生错误
除了主要存储用户记录的user records,最重要的就是page dirctory页目录了,因为关系到记录查询的效率。
页目录规则:(图)
-
所有未删除的记录将被划分为若干个分组,包括伪记录
-
每个组的最后一条记录的n_owned属性标记着当前分组的记录数量
-
每个组的最后一条记录在页面中的地址偏移量提取出来,按顺序放置到靠近页尾的地方,这个地方就称之为页目录(Page Directory),这些偏移量也称为“槽”,每个槽占2字节,页目录就是由多个槽组成的。
分组的规则:
-
Infimum所在的分组,只能有Infimum记录本身,即1条记录
-
Superman所在的分组记录条数只能在1~8条之间,剩下其他分组条数范围在4~8条之间
-
分组实现步骤:
-
初始状态,一个page只有两条记录,两个分组,两个槽,分别记录Infimum和Superman的地址偏移量
-
每插入一条记录,都会从页目录中找到对应记录的主键值比待插入记录的主键值大且差值最小的槽(没懂),然后把该槽对应的记录的n_owned值+1,表示本组又添加了一条记录,直至本组记录数等于8
-
当一个组的记录数等于8后,再插入一条记录时,会讲组中的记录拆分成两个组,其中一个组4条记录,一个组5条记录。拆分过程中会在页目录中新增一个槽,记录这个新增分组中最大的那个记录的偏移量
使用页目录查询记录
我们知道mysql的底层是B+树,树的叶子节点由一个个page连接而成的链表组成,当查询数据时,我们通过高性能的B+树拿到了我们所需要的page,而在单个page中,数据量已经小很多了,利用简单的二分查找就可以将记录拿到。
假设我们拿到一条记录的主键,根据主键去查找当前页中的记录:
-
初始状态下,设置low为0,hight为槽的总数量, 槽的数量也就是分组的数量。由于槽本身记录着其分组最后一条记录的偏移量,所以槽所对应的主键就是槽所在分组的最大主键。使用需要查找的主键跟槽对比,修改low和hight两个变量,最后对比出需要查找的记录具体在哪个槽。
-
知道某个槽(分组)中存放着我想要的记录,则需要把这个槽从小到大逐个遍历,我们拿到的槽的值是不可以用来遍历的,因为槽的值只是当前分组的最大记录,不能反向去遍历。而页目录中的槽都是连续存放的,因此我们需要拿到上一个槽,获取那条记录的下一条记录,也就是next_records值,然后遍历所在分组的所有记录,即可找到该主键对应的记录。
记录头信息详解
插入数据的规则:
在数据页刚生成时是没有User Recoeds部分的,每插入一条记录,都会从Free Page中取出一条记录大小的空间划给User Records,当空闲空间用尽后还在插入数据,则需要申请新的page。
现在回过头再看看用户空间里,每条记录所携带的额外记录,里面的头信息放着很多重要的属性:
-
预留1、2位:预留位,不用
-
delete_flag:标记是否删除。mysql中的做记录删除并没有删除记录所在占的空间,而是讲该记录置为1.如果删除时连带空间也删除,则每次删除一条记录都需要重新排列其他记录,造成性能消耗。因此使用该标记。并且所有被删除的记录就组成一个垃圾链表。记录在该链表中的空间时可重用空间,后续新增数据时,又可能会覆盖掉记录,并占用其空间。
-
min_rec_flag:B+树中的每层非叶子节点中的最小目录项记录会添加该标记
-
n_owned:页面内的记录分成的组,每个组中有一个记录的你n_owned值记录着当前记录组的条数,剩余记录该值均为0
此项为重点,单独提出来讲:
堆是User Records中的记录的排列结构。记录时一条条的排列出来的,(图)heap_n标记记录之间的相对位置,前面的值小,后面的值大。值的大小是比较主键的大小。
当我们插入新的记录之前,页里面就已经存在两条记录了,即Infimum+Superman两条伪记录,也叫虚拟记录。Infimum代表一个页中最小的记录,superman代表最大的记录。这两条记录的位置处于user records的最前面,(图)因此他们的heap_no的值最小。不论插入多少数据,所有用户记录的heap_no都比Infimum大,比Superman小。
Infimum+Superman的结构:5字节的记录头信息+8字节的固定单词组成。
-
Record_type:记录类型,普通记录标记0;非叶节点的目录标记1,Infimum标记2,Superman标记3
-
Next_records:表示到下一条距离的相对位置。该属性记录当前记录的真实数据到下一条记录真实数据的距离。如果某条记录该属性位正数,则下一条记录在该记录的后面。例如:第一条记录该属性值为32,则下一条的真实数据地址在本条记录的真实数据的地址往后+32字节;如果某条记录该属性为负数-145,则下一条记录的真实数据需要往前-145字节。
不难看出,所有记录的next_records属性值形成了一个单向链表,这个链表有最小值Infimum和最大值Superman。当中间的用户记录被删除时,所删除的记录deleted_flag属性置为1,next_records属性置为0,前一条记录的next_records值更改为被删除记录的下一条记录的真实数据地址。(如果刚删除接着又插入回去,会复用刚才的空间,不会申请新的空间)