Hello,大家好,我是IT周瑜,一个爱好研究源码的教育行业创业者,最近在研究MySQL源码,今天给大家分享一个MySQL比较底层的知识点:InnoDB是如何识别普通字段和溢出字段的?
看完本文你就收获:
- 什么是普通字段和溢出字段
- 为什么要进行溢出
- 通过ibd文件分析溢出地址长啥样
- 通过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
,先符合第一个条件,表示是用两个字节来存的字段长度,但是用两个字节存字段长度并不能代表字段一定溢出了,还要符合第二个条件,所以这里的关键就是c0
,c0
对应的二进制为1100
,能符合这两个条件,所以**c0**
就是溢出字段的标记。
通过以上源码,最终可以得出:
06
对应的是id=2的a字段的长度,是普通字段,长度为614 c0
对应的是id=1的a字段的长度,但是c0
表示是溢出字段,14
表示溢出地址占用的字节数(20个字节),看上上图第一条红线部分,而不是字段真实数据占用的字节数,通过溢出地址可以找到真实数据的长度和真实数据。
写在最后
以上,就是本文的内容,介绍了:
- 什么是普通字段和溢出字段
- 为什么要进行溢出
- 通过ibd文件分析溢出地址长啥样
- 通过ibd文件和源码分析了变长字段长度中的溢出标记c0
MySQL源码还是很难的,很佩服写MySQL源码的这帮家伙,牛逼!
关注我(公众号:IT周瑜),向牛人学习,成为牛人!