InnoDB存储引擎的记录格式,数据页的结构

  InnoDB是存储引擎,负责将磁盘的数据按照我们的逻辑概念“表”那样存取,这里首先总结下表中每一行数据是以何种形式被InnoDB存储再磁盘的。InnoDB的行记录存储形式有很多种,compact、dynamic、redundant、compressed等。下面介绍compact格式的行数据结构:

每一行的数据组成

  除了我们自己的真实数据外,为了提高性能和用于存取这行InnoDB需要加上必要的数据,这叫做额外信息。每一行数据都由额外信息 + 真实数据组成。 其中额外信息里又分为变长字段长度列表、null字段列表、记录头信息(记录头信息有很多用于存取行、管理行的数据);真实数据也并不是只有我们自己设置的数据,mysql还会为其添加一些字段例如:事务Id、回滚指针、行Id(没有设置主键时用于唯一标识行)。

额外信息的变长字段长度列表

  如果某个字段设置为变长,mysql必须知道这个字段实际长度,不然mysql取这个字段时根本不知道取多少。变长字段的长度就存放在额外信息的变长字段长度列表里,但是不是顺序的而是倒序的。当然,如果这一行没有变长字段,这一个列表就不存在。例如:表中有如下数据:

a(varchar(255))    |      b (varchar(256))       |      c(varchar(256))
---------------------------------------------------------------------
  sq        |     s.....(128个s)      |     s                    //第1行

第一行有三个变长字段,长度分别是 2B、128B、1B(我们假设字符集是ascii,一个字母一字节)。那么在变长字段长度列表里应该是:| 1  128  2  |,这几个数字占有内存的情况是:最大长度小于256,分配一字节;最大允许长度大于255且实际占用小于128,也分配一字节;最大允许长度大于255,且实际长度大于127则分配两字节。所以长度列表的内存分配应该是 1B + 2B +1B,共4B。

(由于记录变长字段实际大小的数字分配最躲2B空间,所以最多只能表示到65535,即每列最大65535B。)

额外信息的null值列表

  如果某几个列允许为null,那么为null值时不会记录在真实数据而是记录在额外信息里。和变长字段长度列表一样,也是倒序记录,采用bit记录。例如null值列表:0000 1001 ,表示第1个和第4个字段为null值。如果一行没有可以为null的字段,这个列表就不存在。

  值的注意的是:这个列表的内存单位是字节为单位。例如有7个可为null的字段,就需要分配1B记录;如果有9个字段可为null,就需要2B(因为需要9个bit了)。

额外信息的记录头信息

  这里有mysql操作记录所必须的数据,主要有:

  heap_no表示当前记录在记录堆的位置,根据主键从小到大排序。heap_no = x说明这条记录是第x条记录,InnoDB会自动插入一条最小记录(heap_no = 0)和一条最大记录(heap_no = 1)我们自己插的数据heap_no从2开始。

  next_record表示下一条记录的相对位置;这个非常重要,记录了从当前记录的真实数据开始地址到下一条记录真实数据首地址的偏移。例如当前记录真实数据起始地址x,next_record = y,那么下一条记录的真实数据首地址就为 x+y。总之,有了这个相对位置数据,一条条记录就像链表一样连接起来,删除某记录时直接修改前面记录的指针即可。当拿到真实数据的起始地址,向右可以读取每列的实际数据,向左可以读取变长字段长度列表,null值列表,这就是为什么这两个列表要逆序的原因。

  delete_mask表示记录是否删除。没错,实际存储删除记录并不是马上真的将磁盘的数据删掉,而是先标记起来,因为立即删除磁盘的重新排列会有较大的性能消耗。这些标记的行会组成一个垃圾链表,占用的空间叫做可重用空间,新纪录插入时就可能把这些空间的数据删除。如果我们再插入这条记录(主键一样),那么这条记录直接就可以复用,修改一下链表指针即可。

  min_rec_mask表示记录是否是B+树每层非叶子节点中的最小。

  record_type表示这条记录的类型,0普通记录,1表示B+树的非叶子结点,2最小记录,3最大记录。最大最小记录都是InnoDB自己插的。

  n_owned表示当前记录组的记录条数,为了提高查找效率将记录分组,每组最大的那条记录(根据主键排)的owned_no表示自己组的拥有记录条数。

贴个别人的图,不然太不直观:

真实数据的隐藏列

  隐藏列不是我们加上去的数据,是mysql加上去并维护的,但是也属于是真实数据。例如有:DB_ROW_ID,行id唯一标识一条记录;DB_TRX_ID事务id;DB_ROLL_PTR回滚指针等等。

(InnoDB一页16KB,而MySQL限制:不算BLOB、TEXT列,每行大小不超过65535B,因此有可能出现一页装不下一行数据的i情况。compact处理这种情况就是能放进的数据照常存放,然后用这一页的最后20B记录剩下的数据存放在哪些页哪些位置,相当于地址加偏移)

MySQL8中InnoDB的默认行格式是Dynamic,它与compact类似,只是在行溢出的时候有一点不同,只要这一行数据大小超过16KB,就直接用20B记录存放数据的页和位置,想当于这行的真实数据一点也不留点放在当前页。

InnoDB页的结构

  InnoDB页有很多种类型:索引页(存放实际数据),undo日志信息页,insertBuffer页,inode结点信息页。重点了解索引页

  User Records存放记录,插入新记录可以从Free Space分配内存,然后这部分内存就属于User Records,当Free Space不足时就需要下一页了。

Page Directory相当于目录,由一组槽构成,每个槽记录的是对应记录组最大记录的地址,0号槽的记录排序最低,槽号越大记录排序越高。初始情况系统添加的最大记录、最小记录分别在两个槽中,之后每插入一条记录都找到排序和要插入记录最相近的槽,当一个槽达到8个后再插入就直接转变为两个槽,一个4条一个5条。当查找记录时根据槽号二分查找,可以快速找到目标槽,然后去这个槽里的那组记录里遍历链表查找。总之,Page Directory加快了记录的查找速度。

Page Header页面头部,这部分保存了整个页面的状态信息,比如本页中存储了多少记录、多少槽、当前页在b+树的层级、索引ID表示当前页属于哪个索引等。

File Header文件头部,这里记录一些更通用的状态信息,而不是适用某种类型。页号、上一页页号、下一页页号、页面类型等等、属于哪个表、页校验和等等。

File Tralier文件尾部,也是记录页面通用信息;前4B记录了整个页的校验和,当页在内存中修改后就会生成新的校验和,然后写入磁盘时先写入File Header的校验和最后写入File Trailer的校验和,如果某次写入磁盘因为断电等原因没有成功写完,则两个地方的校验和将不一致,最后读取一个页发现头尾的校验和不一致就能发现出错了;后4B则代表最后一次修改时对应的日志序列位置。

原文地址:https://www.cnblogs.com/shen-qian/p/12607385.html