1. 变长字段在磁盘中的存储机制

在MySQL中有些字段的长度是变长的,也就是不固定的,以VARCHAR(10) 类型的字段为例,它里面存放的字符串的长度是不固定的,有可能是“hello” 这么一个字符串,也可能是“a” 这么一个字符串。

案例,这里有一行数据,它的几个字段的类型为 VARCHAR(10), CHAR(1),CHAR(1) , 那么它第一个字段是VARCHAR(10),这个长度是可能变化的,所以这一行数据可能就是类似于: hello a a ,这样子,第一个字段的值是“hello” , 后面两个字段的值都是一个字符,就是一个a。

这里的三个字段,第一个字段的长度是不固定的,后面两个字段的长度都是固定的1个字符。

如果此时是有两行数据,第一条是: hello a a,另一条是 hi a a 。如果把这两条数据写入一个磁盘文件里,两行数据是挨在一起的,那么这个时候在一个磁盘文件里可能有下面的两行数据:

 hello a a hi a a

说两行是不对的,实际上是两条数据在底层磁盘文件里紧紧挨着存储的。这一结论适用于一个表里的很多行数据在落盘后的存储结果。

2. 引入变长字段的长度列表,来解决一行数据的读取问题

问题引出:

在 “hello a a hi a a” 这行数据落盘后,想要读取出来,是有难度 。因为落盘后的数据,表的第一个字段是VARCHAR(10)类型的,而实际上一个字段的长度是不可知的。

在不知道一行数据的每个字段到底是多少长度的情况下,胡乱的去读取,会发生读取出来的可能是“hello a a hi” 的一行数据,或者是“ hello a ” 这一行数据,而这显然是不正确的。

方案:引入变长字段的长度列表

为解决上述问题,在存储每一行数据的时候,都会保存一下它的变长字段的长度列表,这就解决了一行数据的读取问题。

也就是说,在存储“hello a a” 这行数据的时候,要带上一些额外的附加信息,比如第一块就是它里面的变长字段的长度列表。

我们看到“hello” 的长度是5,十六进制就是0x05,此时会在“hello a a” 前面补充这些额外信息。最终这行数据在磁盘文件里存储的时候,是如下的格式:

0x05 null值列表 数据头 hello a a。

而如果是两行数据放在一起存储在磁盘文件里,存储格式如下所示:

0x05 null值列表 数据头 hello a a 0x02 null值列表  数据头 hi a a

引入变长字段的长度列表如何解决变长字段的读取问题

在引入变长字段的长度列表后,再去读取“ hello a a ” 这行数据,首先会知道这个表里的三个字段的类型是VARCHAR(10) CHAR(1) CHAR(1)。

此时会发现第一行数据的开头有一个变长字段的长度列表,里面会读取到一个0x05这个十六进制的数字,发现第一个变长字段的长度是5,也是按照长度为5,读取出来第一个字段的值,就是“hello”。

接着因为知道后续两个字段都是CHAR(1),长度都是固定的1个字符,于是此时就依次按照长度为1 读取出来后续两个字段的值,分别是“a” “a” ,最终就能读取出来 “hello a a” 这一行数据。

读取第二行数据的方式与第一行一致,通过它第一个变长字段的长度信息 0x02可以读取到“hi” ,再一次读取到CHAR(1) 的两个字符,最终读取出来 “hi a a”这行数据。

3.有多个变长字段,如何存放他们的长度?

例如,一行数据有VARCHAR(10) VARCHAR(5) VARCHAR(20) CHAR(1) CHAR(1) ,一共5个字段,其中三个是变长字段,此时假设一行数据是这样的: hello hi hao a a 。

此时在磁盘中存储的,必须在它开头的变长字段长度列表中存储几个变长字段的长度,一定要注意一点,它这里是逆序存储的。

也就是说先存放VARCHAR(20)这个字段的长度,然后存放VARCHAR(5)这个字段的长度,最后存放VARCHAR(10)这个字段的长度。

那么,现在hello hi hao三个字段的长度分别是0x05 0x02 0x03,但是实际存放在变长字段长度列表的时候,是逆序放的,所以一行数据实际存储可能是下面这样的:

0x03 0x02 0x05 null值列表 头字段 hello hi hao a a

4.扩展思考

(1)为什么MySQL在把一行一行的数据存储在磁盘上的时候,要采取这种 “0x05 null值列表 数据头 hello  a a 0x02 null值列表 数据头 hi a a”很多行数据都紧紧挨在一起的方式?

答:多行紧凑的好处: 序列化反序列化时的开销小;不易有内存碎片;定位数据时比较快速。

(2)为什么MySQL不能用Java里面的序列化的那种方式?把很多行的数据做成一个大的对象,然后给它序列化一下写入到磁盘文件里,从磁盘里读取的时候压根儿不用care什么行存储格式,直接反序列化一下, 把数据就可以从磁盘文件里拿回来了。

答:因为不需要每次都获取全部数据,只获取其中一两个字段的话,按照现在的序列化方式,就可以计算出字段对应的偏移量来获取;java对象的存储其实也是这样的,看unsafe类就知道了。