首页 > 数据库 >MySQL学习(四)---->InnoDB数据页结构

MySQL学习(四)---->InnoDB数据页结构

时间:2023-03-15 15:47:45浏览次数:40  
标签:插入 删除 记录 next ---- record InnoDB MySQL

  页是InnoDB管理存储空间的基本单位,一个页的大小一般是16KBInnoDB为了不同的目的而设计了许多种不同类型的,比如存放表空间头部信息的页,存放Insert Buffer信息的页,存放INODE信息的页,存放undo日志信息的页等等等等。当然了,如果我说的这些名词你一个都没有听过,就当我放了个屁吧~ 不过这没有一毛钱关系,我们今儿个也不准备说这些类型的页,我们聚焦的是那些存放我们表中记录的那种类型的页,官方称这种存放记录的页为索引(INDEX)页,鉴于我们还没有了解过索引是个什么东西,而这些表中的记录就是我们日常口中所称的数据,所以目前还是叫这种存放记录的页为数据页吧。

数据页(索引页):存放记录的页。

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

从图中可以看出,一个InnoDB数据页的存储空间大致被划分成了7个部分,有的部分占用的字节数是确定的,有的部分占用的字节数是不确定的。下边我们用表格的方式来大致描述一下这7个部分的存储内容:

名称

中文名

占用空间大小

简单描述

File Header

文件头部

38

字节

页的一些通用信息

Page Header

页面头部

56

字节

数据页专有的一些信息

Infimum + Supremum

最小记录和最大记录

26

字节

两个虚拟的行记录

User Records

用户记录

不确定

实际存储的行记录内容

Free Space

空闲空间

不确定

页中尚未使用的空间

Page Directory

页面目录

不确定

页中的某些记录的相对位置

File Trailer

文件尾部

8

字节

校验页是否完整

在页的7个组成部分中,我们自己存储的记录会按照我们指定的行格式存储到User Records部分。但是在一开始生成页的时候,其实并没有User Records这个部分,每当我们插入一条记录,都会从Free Space部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了,这个过程的图示如下:

记录头信息

为了故事的顺利发展,先创建一个表:

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

从图中可以看到,我们特意把记录头信息的5个字节的数据给标出来了,说明它很重要,我们再次先把这些记录头信息中各个属性的大体意思浏览一下(我们目前使用Compact行格式进行演示):

名称

大小(单位:bit)

描述

预留位1

1

没有使用

预留位2

1

没有使用

delete_mask

1

标记该记录是否被删除

min_rec_mask

1

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

n_owned

4

表示当前记录拥有的记录数

heap_no

13

表示当前记录在记录堆的位置信息

record_type

3

表示当前记录的类型,0

表示普通记录,1

表示B+树非叶节点记录,2

表示最小记录,3

表示最大记录

next_record

16

表示下一条记录的相对位置

由于我们现在主要在讲解记录头信息的作用,为了理解上的方便,我们只在page_demo表的行格式演示图中画出有关的头信息属性以及c1c2c3列的信息(其他信息没画不代表它们不存在,只是为了理解上的方便在图中省略了~),简化后的行格式示意图就是这样:

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

mysql> INSERT INTO page_demo VALUES(1, 100, 'aaaa'), (2, 200, 'bbbb'), (3, 300, 'cccc'), (4, 400, 'dddd');
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

为了方便大家分析这些记录在User Records部分中是怎么表示的,把记录中头信息和实际的列数据都用十进制表示出来了(其实是一堆二进制位),所以这些记录的示意图就是:

看这个图的时候需要注意一下,各条记录在User Records中存储的时候并没有空隙,这里只是为了大家观看方便才把每条记录单独画在一行中。我们对照着这个图来看看记录头信息中的各个属性是啥意思:

  • delete_mask
    这个属性标记着当前记录是否被删除,占用1个二进制位,值为0的时候代表记录并没有被删除,为1的时候代表记录被删除掉了。
    啥?被删除的记录还在垃圾链表中么?是的,摆在台面上的和背地里做的可能大相径庭,你以为它删除了,可它还在真实的磁盘上[摊手](忽然想起冠希~)。这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要性能消耗,所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的可重用空间,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。可重用空间,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。

小贴士:
将这个delete_mask位设置为1和将被删除的记录加入到垃圾链表中其实是两个阶段,后边在介绍事务的时候会详细唠叨删除操作的详细过程,稍安勿躁。

  • min_rec_mask
    B+树的每层非叶子节点中的最小记录都会添加该标记,插入的四条记录的min_rec_mask值都是0,意味着它们都不是B+树的非叶子节点中的最小记录。
  • n_owned
    这个暂时保密,稍后它是主角~
  • heap_no
    这个属性表示当前记录在本中的位置,从图中可以看出来,我们插入的4条记录在本中的位置分别是:2345。是不是少了点啥?是的,怎么不见heap_no值为01的记录呢?
    InnoDB自动给每个页加了两个记录,由于这两个记录并不是我们自己插入的,所以有时候也称为伪记录或者虚拟记录。这两个伪记录一个代表最小记录,一个代表最大记录,等一下哈~,记录可以比大小么?
    是的,记录也可以比大小,对于一条完整的记录来说,比较记录的大小就是比较主键的大小。比方说我们插入的4行记录的主键值分别是:1234,这也就意味着这4条记录的大小从小到大依次递增。

小贴士:
请注意我强调了对于`一条完整的记录`来说,比较记录的大小就相当于比的是主键的大小。后边我们还会介绍只存储一条记录的部分列的情况,敬请期待~

但是不管我们向中插入了多少自己的记录,InnoDB都规定定义的两条伪记录分别为最小记录与最大记录。这两条记录的构造十分简单,都是由5字节大小的记录头信息和8字节大小的一个固定的部分组成的,如图所示 由于这两条记录不是我们自己定义的记录,所以它们并不存放在User Records部分,他们被单独放在一个称为Infimum + Supremum的部分,如图所示:
  • 从图中我们可以看出来,最小记录和最大记录的heap_no值分别是01,也就是说它们的位置最靠前。
  • record_type
    这个属性表示当前记录的类型,一共有4种类型的记录,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录。从图中我们也可以看出来,我们自己插入的记录就是普通记录,它们的record_type值都是0,而最小记录和最大记录的record_type值分别为23
    至于record_type1的情况,我们之后在说索引的时候会重点强调的。
  • next_record这玩意儿非常重要,它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。比方说第一条记录的next_record值为32,意味着从第一条记录的真实数据的地址处向后找32个字节便是下一条记录的真实数据。如果你熟悉数据结构的话,就立即明白了,这其实是个链表,可以通过一条记录找到它的下一条记录。但是需要注意注意再注意的一点是,下一条记录指得并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。而且规定Infimum记录(也就是最小记录)的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是Supremum记录(也就是最大记录),为了更形象的表示一下这个next_record起到的作用,我们用箭头来替代一下next_record中的地址偏移量:

从图中可以看出来,我们的记录按照主键从小到大的顺序形成了一个单链表。最大记录next_record的值为0,这也就是说最大记录是没有下一条记录了,它是这个单链表中的最后一个节点。如果从中删除掉一条记录,这个链表也是会跟着变化的,比如我们把第2条记录删掉:
mysql> DELETE FROM page_demo WHERE c1 = 2;
Query OK, 1 row affected (0.02 sec)
删掉第2条记录后的示意图就是:
  • 从图中可以看出来,删除第2条记录前后主要发生了这些变化:
    • 第2条记录并没有从存储空间中移除,而是把该条记录的delete_mask值设置为1
    • 第2条记录的next_record值变为了0,意味着该记录没有下一条记录了。
    • 第1条记录的next_record指向了第3条记录。
    • 最大记录n_owned值从5变成了4
  • 所以,不论我们怎么对页中的记录做增删改操作,InnoDB始终会维护一条记录的单链表,链表中的各个节点是按照主键值由小到大的顺序连接起来的。

小贴士:

你会不会觉得next_record这个指针有点儿怪,为啥要指向记录头信息和真实数据之间的位置呢?为啥不干脆指向整条记录的开头位置,也就是记录的额外信息开头的位置呢?

因为这个位置刚刚好,向左读取就是记录头信息,向右读取就是真实数据。我们前边还说过变长字段长度列表、NULL值列表中的信息都是逆序存放,这样可以使记录中位置靠前的字段和它们对应的字段长度信息在内存中的距离更近,可能会提高高速缓存的命中率。

再来看一个有意思的事情,因为主键值为2的记录被我们删掉了,但是存储空间却没有回收,如果我们再次把这条记录插入到表中,会发生什么事呢?

mysql> INSERT INTO page_demo VALUES(2, 200, 'bbbb');
Query OK, 1 row affected (0.00 sec)

从图中可以看到,InnoDB并没有因为新记录的插入而为它申请新的存储空间,而是直接复用了原来被删除记录的存储空间。

小贴士:
当数据页中存在多条被删除掉的记录时,这些记录的next_record属性将会把这些被删除掉的记录组成一个垃圾链表,以备之后重用

总结

  1. InnoDB为了不同的目的而设计了不同类型的页,我们把用于存放记录的页叫做数据页
  2. 一个数据页可以被大致划分为7个部分,分别是
    • File Header,表示页的一些通用信息,占固定的38字节。
    • Page Header,表示数据页专有的一些信息,占固定的56个字节。
    • Infimum + Supremum,两个虚拟的伪记录,分别表示页中的最小和最大记录,占固定的26个字节。
    • User Records:真实存储我们插入的记录的部分,大小不固定。
    • Free Space:页中尚未使用的部分,大小不确定。
    • Page Directory:页中的某些记录相对位置,也就是各个槽在页面中的地址偏移量,大小不固定,插入的记录越多,这个部分占用的空间越多。
    • File Trailer:用于检验页是否完整的部分,占用固定的8个字节。
  1. 每个记录的头信息中都有一个next_record属性,从而使页中的所有记录串联成一个单链表
  2. InnoDB会把页中的记录划分为若干个组,每个组的最后一个记录的地址偏移量作为一个,存放在Page Directory中,所以在一个页中根据主键查找记录是非常快的,分为两步:
    • 通过二分法确定该记录所在的槽。
    • 通过记录的next_record属性遍历该槽所在的组中的各个记录。
  1. 每个数据页的File Header部分都有上一个和下一个页的编号,所以所有的数据页会组成一个双链表
  2. 为保证从内存中同步到磁盘的页的完整性,在页的首部和尾部都会存储页中数据的校验和和页面最后修改时对应的LSN值,如果首部和尾部的校验和和LSN值校验不成功的话,就说明同步过程出现了问题。

 

 

标签:插入,删除,记录,next,----,record,InnoDB,MySQL
From: https://www.cnblogs.com/donleo123/p/17218748.html

相关文章

  • CSRF Failed: CSRF token missing or incorrect
    背景: post请求一个接口: 后端视图中继承的是APIView,其登陆认证 authentication_classes=[xxx]是注释的,然后浏览器开发者模式中Network中Preview中报错如题.解......
  • Mysql基础知识
    Mysql基础知识Mysql概述数据库相关概念数据库:存储数据的仓库,数据是有组织的进行存储,Database(DB)。数据库管理系统:操纵和管理数据库的大型软件,DataBaseManagement......
  • VMMVlight实敲总结 纯新手向
    VMMVlight实敲总结跟敲教程为:https://www.bilibili.com/video/BV1FS4y1o7nU?p=17&vd_source=960e69b67c5551f583c23efeb5df58cc导入VMMVlight框架,从nuget包里添加,添加后......
  • uniapp,常用工具函数
    /*处理文件路径*/exportdefault{//拨打电话callPhone(phone){uni.makePhoneCall({phoneNumber:phone//仅为示例});......
  • A类,B类,AB类,D类音频放大器的区别
    根据放大电路的导电方式不同,音频功放电路按照模拟和数字两种类型进行分类,模拟音频功放通常有A类,B类,AB类, G类,H类 TD功放,数字电路功放分为D类,T类。 下文对以上的功放......
  • uni-app+thinkphp: 单图片文件上传(hbuilderx 3.7.3)
    一,js代码:<template><view><imagemode="aspectFit":src="imageSrc"@tap="chooseImage"style="background:gray;width:200rpx;height:200rpx......
  • Markdown学习
    Markdown学习二级标题三级标题四级标题设置标题用井号+空格+标题名来设置(一个井号一级标题、两个井号二级标题、以此类推最多六级标题)字体helloworld!(粗体前后加俩......
  • django-filter用法
    一.环境准备pipinstallDjango==2.2-ihttps://pypi.douban.com/simplepipinstalldjangorestframework==3.10-ihttps://pypi.douban.com/simplepipinstalldjan......
  • 线性回归
    线性回归1、概念线性回归是最基础的回归模型。观察到x与y的关系(模型选择),y=ax+b,建立线性回归模型。通过优化方法设法拟合数据,得到最优的a,评估该模型是否准确,查看训练集......
  • YOLOv8训练
    以下文件全放在ultralytics/yolo/data目录下一、使用labelimg进行数据标记1.按以下顺序建立文件夹VOCdevkit→VOC2007→Annotations、Images、labels、pre......