超纲了!什么是溢出字段?InnoDB是如何识别普通字段和溢出字段的?(附十张图、IBD文件解析、源码解析)

97 阅读6分钟

Hello,大家好,我是IT周瑜,一个爱好研究源码的教育行业创业者,最近在研究MySQL源码,今天给大家分享一个MySQL比较底层的知识点:InnoDB是如何识别普通字段和溢出字段的?

看完本文你就收获:

  1. 什么是普通字段和溢出字段
  2. 为什么要进行溢出
  3. 通过ibd文件分析溢出地址长啥样
  4. 通过ibd文件和源码分析了变长字段长度中的溢出标记c0

什么是溢出字段

InnoDB中,记录的默认行格式为Dynamic,还有另外一种行格式为Compact,他们的区别就在于对溢出字段的不同处理。

InnoDB中,一行记录是存在页中的,一行记录除开有用户自定义的主键和其他字段外,还有变长字段长度列表、NULL值列表、记录头、隐藏字段事务ID、隐藏字段回滚指针,对于下面定义的这张表:

create table t(
  id int primary key, 
  a varchar(10000) not null
);

由于没有可为NULL的字段,所以没有NULL值列表,假如现在表中有四条记录,那么页中记录的结构为:

不要觉得疑惑,主键字段是一定会放在所有字段最前面的!

假如id=3的记录,对应的a字段的内容为:

insert into t values(3, repeat('a', 10000));

此时对于id=3这条记录来说,a字段就是溢出字段了,因为存的数据太多了,InnoDB此时会对该字段进行溢出的处理。

Compact行格式会将溢出字段的前768个字节存在当前行中,另外再存一个其他字节的溢出地址,通过这个溢出地址可以找到其他字节,Compact行格式的处理方式为:

Dynamic和Compact的区别就在于,只会在当前行中存该字段的溢出地址,溢出地址中存这个字段的所有内容,Dynamic行格式的处理方式为:

为什么要进行溢出

进行溢出的目的是,使得INDEX页中能存更多的记录,从而提高B+树的效率,如果有十条非常大的记录,如果不进行列溢出,那B+树中可能需要十页来存,每一条记录占用一页,而进行列溢出,虽然也需要十页,但是B+树中只有一页,注意溢出页是不挂在B+树中的,这样减少了B+树的体积,提高了B+树的效率。

回到本文的重点:InnoDB是如何识别普通字段和溢出字段的?其他相关知识大家可以关注我公众号:IT周瑜,后续持续进行分享。

我会通过解析表空间ibd文件和相应源码来进行分析。

先创建一张表:

create table t(
  id int primary key, 
  a varchar(10000) not null
);

新增一条big record:

insert into t values(1, repeat('a', 10000));

再新增一条normal record:

insert into t values(2, 'dadudu');

ibd文件中的溢出页和溢出地址

新增数据后,我们来查看ibd文件中具体内容,这两个是页号,第3页和第4页,其中第4页其实就是溢出页

这是两条记录的的id字段,id=1和id=2,为什么是80000001,而不是00000001呢,本文先不分析了,关注我(公众号:IT周瑜),后面再分析

而id字段后面的7个字节为事务id,再往后面的6个字节为回滚指针,本文不需要关心,后面分析MVCC时候会用到

再往后就是两条记录中字段a的值了

比较明显的是id=2的记录,就是64 61 64 75 64 75,就是'dadudu'对应的ASCII码,而id=1的记录中字段a的值就是溢出页地址,占了20个字节,比较明显的是00 00 00 04表示第4页,而00 00 00 26表示第4页中的第38个字节(十六进制26为十进制38),也就是

而在往后就是00 00 27 10,对应十进制为10000,也就是a字段真实内容的长度,因此从第3页找到溢出地址后(溢出页页号和偏移地址),能先得到字段的总长度,然后再按这个长度往后取10000个字节就得到a字段的真实数据。

不过,由于我们是提前知道了id=1的中a字段存的溢出地址,所以可以正常的分析出来,那InnoDB底层是如何知道a字段中存的是溢出地址而不是普通字段内容的呢?这就跟变长字段长度列表有关了。

变长字段长度列表

所谓变长字段长度列表,就是用来记录一行记录中所有varchar类型字段到底存了多少个字节数据的,我们现在只有一个varchar字段,所以只需要记录这一个字段的长度就可以了,另外,变长字段长度列表是存在一行记录的最前面的。

可以看出,

  • id=1的记录变长字段长度列表占两个字节为14 c0
  • id=2的记录变长字段长度列表占一个字节为06

比较明显的是,id=2的a字段为'dadudu',所以占6个字节是对的。

但是id=1的14 c0该如何理解呢?

这里要结合源码来分析了。

源码分析

以下是InnoDB中来判断某个字段是不是溢出地址的源码实现。

假如传进来的是06,那么第一个判断就不符合条件了。

假如传进来的是14 c0,先符合第一个条件,表示是用两个字节来存的字段长度,但是用两个字节存字段长度并不能代表字段一定溢出了,还要符合第二个条件,所以这里的关键就是c0c0对应的二进制为1100,能符合这两个条件,所以**c0**就是溢出字段的标记

通过以上源码,最终可以得出:

  1. 06对应的是id=2的a字段的长度,是普通字段,长度为6
  2. 14 c0对应的是id=1的a字段的长度,但是c0表示是溢出字段,14表示溢出地址占用的字节数(20个字节),看上上图第一条红线部分,而不是字段真实数据占用的字节数,通过溢出地址可以找到真实数据的长度和真实数据。

写在最后

以上,就是本文的内容,介绍了:

  1. 什么是普通字段和溢出字段
  2. 为什么要进行溢出
  3. 通过ibd文件分析溢出地址长啥样
  4. 通过ibd文件和源码分析了变长字段长度中的溢出标记c0

MySQL源码还是很难的,很佩服写MySQL源码的这帮家伙,牛逼!

关注我(公众号:IT周瑜),向牛人学习,成为牛人!