博客园  :: 首页  :: 新随笔  :: 订阅 订阅  :: 管理

MySQL存储引擎-InnoDB数据页

Posted on 2024-02-06 16:59  面具下的戏命师  阅读(172)  评论(0编辑  收藏  举报

MySQL存储引擎-InnoDB数据页

MySQL一个数据页默认16kb,MySQL为了不同目的涉及了很多类型的数据页,如undo页、Change Buffer页等等。我们这里只关心存放数据的页,即索引(INDEX)页。一个数据页的存储空间大致被划分为7部分,分别为:

1、FIle Header  文件头  38字节

2、Page Header 页面头  56字节

3、Infimum + Supermum  页面中的最小记录和最大记录   26字节

4、User Records  用户记录 不确定

5、Free Space  空闲空间  不确定

6、Page Directory  页目录  不确定

7、File Trailer  文件尾   8字节

一、记录在页中的存储

在一个新生成的页中,没有User Records部分,每有记录插入,都会从Free Space部分申请一个记录大小,然后划分到User Records部分。当Free Space空间用完后,再有记录插入就需要申请新的页了。我们这里再创建一个示例表:

create table page_demo(
  c1 int,
  c2 int,
  c3 varchar(10000),
  primary key(c1)
) charset = ascii row_format = compact;
insert into page_demo values(1,100,'aaaa'),(2,200,'bbbb'),(3,300,'cccc'),(4,400,'dddd');

这里我们使用 hexdump 命令来查看一下数据的存储格式。

我们使用表格和十进制数据来分析一下每行记录是怎么表示的。 因为该示例表有主键,因此没有隐藏列 rowid。注:int类型占4个字节。 保留个疑问,我们可以看到c2列就是 100、200、300、400的有符号整数。 但是c1列比较奇怪,这里可以验证和主键有关系。

正常列就是按照数字进行存储的。至于主键列是怎么存储的,此处保留一个问题。

以下表:记录头信息的后6列是对记录头信息的拆分, 其中包含两个1字节的预留位未画出。

行号 变长字段 NULL值列表 记录头信息 delete_flag min_rec_flag n_owned heap_no record_type nex_record trx_id roll_pointer c1列 c2列 c3列
第一行 04 00 00 00 10 00 20 0 0 0 2 0 32 80 00 00 01 00 00 13 94 79 79 f0 00 00  01 72 01 10 80 00 00 64 61 61 61 61
第二行 04 00 00 00 18 00 20 0 0 0 3 0 32 80 00 00 02 00 00 13 94 79 79 f0 00 00 01 72 01 1c 80 00 00 c8 62 62 62 62
第三行 04 00 00 00 20 00 20 0 0 0 4 0 32 80 00 00 03 00 00 13 94 79 79 f0 00 00 01 72 01 28 80 00 01 2c 63 63 63 63
第四行 04 00 00 00 28 ff 91 0 0 0 5 0  -111 80 00 00 04 00 00 13 94 79 79 f0 00 00 01 72 01 34 80 00 01 90 64 64 64 64

注:负数用补码表示。 -111的原码为 1 000 0000 0110 1111, 反码1 1111111 1001 0000,补码1 1111111 1001 0001,即16进制的ff91。

如表所示:delete_flag标志记录是否被删除,值为1代表被删除了。一条记录删除之后还是存在于磁盘的。打个删除标记加入垃圾链表,这部分空间就可以复用了。

min_rec_flag B+ 树每层非页节点最小目录项记录会添加该标记

heap_no 表示一条记录在堆中的相对位置,插入的4条记录分别为 2、3、4、5。 0和1是两条伪记录,一个代表最小即可 Infimum , 一个代表最大记录 Supermum。这两条记录的构造比较简单,都是5字节的记录头信息和8字节的固定单词组成,从上图页可以看出。另外:heap_no值一旦分配就不会发生改动了

record_type: 记录类型, 1表述非叶节点的目录项记录、 2表示Infimum记录、 3表示Supermum记录、0表示普通记录。

next_record: 表示当前真实数据到下一条真实数据的距离,32表示往后数32个字节。正数表示下一条记录在上一条记录的后面,负数表示下一条记录在当前记录的前面。最后一条记录的下一条指向的是Supermum记录。 下一条记录是按照主键值由小到大顺序排列的下一条记录。 如果删除第2条记录,那它的next_record会变为0,第一条就指向了第3条,还是形成单向链表。

二、页目录(Page Directory)

现在我们知道了数据是按照主键从小到大串联的单向链表。但是查找的时候为了提升查询速度,InnoDB设计了页目录。过程如下:

1、降所有的正常记录(包括Infimum和Supermum)进行分组,不包括已经移除到垃圾链表的即可

2、每个组的最后一条记录为大哥,其余的为小弟。大哥记录头信息中的 n_owned 属性表示该组共有几条记录

3、将每个组中最后一条记录在页面中的地址偏移量 (即该记录真实数据与页面第0个字节的距离)提取出来,按顺序存储到靠近页尾部的地方,这个地方就是Page Directory。 页目录中这些地址偏移量称为槽(Slot),每个槽占2字节,页目录就是多个槽组成的。

规定,Infimum记录独占1个分组,Supermum记录所在的分组的记录条数只能在 1~8之间,其他分组的条数需要在 4 ~ 8之间。各个槽之间是挨着的,所以,在一个数据页查找指定主键值的记录,过程分两步

1、通过二分法确定该记录所在分组对应的槽,然后找到该槽所在分组中主键值最小的那条记录。

2、通过记录的next_record属性遍历该槽所在组中的各个记录

三、页面头部(Page Header)

为了得到存储在数据页中的记录的状态信息,如数据页中存储了多少条记录,Free Space在页面中的地址偏移量,页目录中存储了多少槽等。InnoDB在数据页中定义了一个Page Header部分,占用固定的56字节。 具体用途如下:

状态名称 占用空间大小(字节) 描述
PAGE_N_DIR_SLOTS 2 页面中的槽数量
PAGE_HEAD_TOP 2 还未使用的空间最小地址, 也就是说从该地址之后就是Free Space
PAGE_N_HEAP 2 第1位表示本记录是否为紧凑型记录,剩余15位表示本页的堆中的记录数量 (包括Infimum和Supermum以及标记为已删除的记录)
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和Supermum以及标记为已删除的记录)
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+树的根页面中定义

PAGE_DIRECTION: 如果新插入一条记录的主键比上一条记录的主键值大,我们称为记录的插入方向为右边,反之为左边。PAGE_DIRECTION就是用来表示最后一条记录的插入方向

PAGE_N_DIRECTION:如果连续几次插入新记录的方向都是一致的,InnoDB会把沿着同一方向插入的记录条数记下来,用PAGE_N_DIRECTION表示。

四、文件头(File Header)