Mysql工作原理——InnoDB数据页结构

数据页结构

数据页代表这块16KB大小的存储空间可以被划分为过个部分,不同部分有不同功能:

在这里插入图片描述

在这里插入图片描述

记录在页中的存储

我们自己存储的记录会按照我们制定的航格式存储到User Records部分。

但是在一开始生成页的时候,并没有这个部分,每当插入一条记录,都会从Free Space部分去申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间全部被替代之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页。

在这里插入图片描述

记录头信息的秘密

先创建一个表:

mysql> CREATE TABLE page_demo( 
-> c1 INT, 
-> c2 INT, 
-> c3 VARCHAR(10000),
-> PRIMARY KEY (c1) 
-> ) CHARSET=ascii ROW_FORMAT=Compact; 
Query OK, 0 rows affected (0.03 sec)

新创建的page_demo表有3个列,其中c1和c2列是用来存储整数的,c3列是用来存储字符串的。需要注意的是,我们把 c1 列指定为主键,所以在具体的行格式中InnoDB就没必要为我们去创建那个所谓 的 row_id 隐藏列了。而且我们为这个表指定了ascii字符集以及Compact的行格式。所以这个表中记录的行格式示意图就是这样的:

在这里插入图片描述

下边我们试着向page_demo表中插入几条记录:

在这里插入图片描述

  • delete_mask:这个属性标记着当前记录是否被删除,占用1个二进制位,0代表记录并没有被删除,1代表记录被删除。

这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要耗费性能,所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的垃圾链表,这个链表中的记录占用的空间称为可重用空间,之后如果有新记录插入,可能把这些被删除的记录占用的存储空间覆盖掉。

  • min_rec_mask:B+树的每层非叶子节点中的最小记录都会添加该标记。

  • heap_no:这个属性表示当前记录在本页中的位置。从图中看出4条记录在本页中的位置分别是2、3、4、5,而0、1记录是自动添加的连个记录,称为伪记录虚记录,这两个记录一个代表最小记录一个代表最大记录。

对于一条完整的记录来说,比较记录的大小就是比较主键的大小。

这两条记录的构造十分简单,都是由5字节大小的记录头信息和8字节大小的一个固定的部分组成的。

在这里插入图片描述

由于这两条记录并不是我们自己定义的,所以并不存放在页的User Records部分,而是被单独放在一个称为Infimum+Supremum的部分:

在这里插入图片描述

  • record_type:这个属性表示当前记录类型,一共有四种类型,0表示普通记录,1表示B+数非叶子节点,2表示最小记录,3表示最大记录。我们自己插入的记录就是普通记录。

  • next_record :表示**从当前记录的真实数据到下一条记录的真实数据的地址偏移量。**值得注意的是,下一条记录指的并不是按照我们插入顺序的下一条记录,而是按照主键由小到大的下一条记录。

在这里插入图片描述

如果删除一条记录这个链表也会跟着变化:

在这里插入图片描述

可以看出:

  • 第二条记录并没有从存储空间中移除,而是把该条记录的delete_mask值设置为1;
  • 第二条记录的next_record值变成了0,意味着该记录没有下一条记录了;
  • 第一条记录的next_record指向第三条记录;
  • 最大记录的n_owned值从5变成了4。

不管怎么对页中的记录做增删改操作,InnoDB始终会维护一条记录的单链表,链表中各个节点是按照主键值由小到大的顺序连接起来的。

再次把第二条记录插入:

在这里插入图片描述
可以看出,InnoDB并没有因为新纪录的插入而为它申请新的存储空间,而是直接复用了原来被删除的记录的存储空间。

当数据页中存在多条被删除掉的记录时,这些记录的next_record属性会将这些被删除掉的记录组成一个垃圾链表,以备之后重用这部分存储空间。

Page Directory (页目录)

InnoDB设计目录过程:

  1. 将所有正常的记录划分为几个组;
  2. 每个组的最后一条(组内最大的那条)的头信息的n_owned属性表示该记录拥有多少条记录,也就是该组内共有几条记录。
  3. 将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方,这个地方就是所谓的page Directory,也就是页目录,页目录中的这些地址偏移量称为,所以这些页目录是由槽组成的。

在这里插入图片描述

  • 现在页目录部分有两个槽,意味着我们的记录被分为两个组,槽1中为112,代表最大记录的地址偏移量(从页面的0字节开始数,数112个字节),槽0中的值是99,代表最小记录的地址偏移量。
  • 最小记录的n_owned值为1,代表以最小记录结尾的这个分组中只有1条记录,也就是最小记录本身。
  • 最大记录的n_owned置为5,代表最大记录结尾的这个分组中只有5条记录,包括最大记录本身和我们插入的4条记录。

直观图:
在这里插入图片描述
分组策略

对于最小记录所在分组只能有1条记录,最大记录所在分组拥有的记录条数只能在1\~8条之间,剩下的分组中记录的条数范围只能在时4~8条之间

分组步骤:

  • 初始情况下一个数据页里只有最小记录和最大记录两条,它们属于两个分组;
  • 之后每插入一条记录,都会从页目录中找到主键值比本记录的主键值大并且差值最小的槽,然后把该槽对应记录的n_owned值加1,本组内又添加一条记录,直到该组内的记录数等于8个。
  • 在一个组中的记录数等于8个后再插入一条时,会将组内的记录拆分为两个组,一个组中4条记录,另一个组中5条记录。这个过程会在页目录中新增一个槽来记录这个新分组中最大的那条记录的偏移量。

一个数据页中查找指定主键值的记录的过程分为两步:

  1. 通过二分法确定该记录所在槽,并找到该槽中主键值最小的那条记录;
  2. 通过记录的next_record属性遍历该槽所在组中的各个记录。
Page Header(页面头部)

用来记录该数据页中存储的记录的状态信息。

在这里插入图片描述

File Header(文件头部)

File Header针对各种类型的页都通用,也就是说不同类型的页都会以File Header最为第一组成部分。描述一些通用信息,如这个页编号是多少,它的上一页、下一页是谁,这个部分笃定38个字节。

在这里插入图片描述在这里插入图片描述

  • FIL_PAGE_SPACE_OR_CHKSUM:当前页面的校验和;

  • FIL_PAGE_OFFSET:每一个页都有一个单独的页号,InnoDB通过页号可以来唯一定位一个页。

  • FIL_PAGE_TYPE:当前页的类型,InnoDB为了不同的目的而把页分为不同类型。

在这里插入图片描述

  • FIL_PAGE_PREV和FIL_PAGE_NEXT

InnoDB都是以页为单位存放数据的,有时候存放某种类型的数据占用的空间非常大,InnoDB可能不可以一次性为这么多数据分配一个非常大的存储空间,如果分散到多个不连续的页中存储的话需要把这些页关联起来。FIL_PAGE_PREV和FIL_PAGE_NEXT就分别代表本页的上一个和下一个页的页号。通过建立一个双向链表把许多的页串联起来,而无须这些页在物理上真正连接。不过值得注意的是并不是所有类型的页都有上一个和下一个页的属性

在这里插入图片描述

File Trailer

我们知道InnoDB存储引擎会把数据存储在磁盘上,但是磁盘速度太慢,需要以页为单位把数据加载到内存中处理,如果该页中的数据在内存中被修改了,那么修改后的某个时间需要把数据同步到磁盘中。但是在同步了一半是断电了。为了检测一个页是否完整,在每一个页的尾部都加了一个File Trailer部分,由8个字节组成,分为2个小部分:

  • 前4个字节代表页的校验和
  • 后4个字节代表页面被最后修改时对应的日志序列位置。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值