3.2 Compact行格式
compact是一种经典的行格式,它也是mysql5.1
的默认行格式,我们把它作为讲解的重点介绍。其示意图如下。
现在我们对这个结构进行里的各个部分进行依次讲解。
3.2.1 变长字段长度列表
执行如下语句,创建一张新的数据表。
插入几条数据。
我们知道,在mysql数据库中,VARCHAR(M),VARBINARY(M),TEXT,BLOB类型都是变长的。我们在上面指定col1时设置的长度是8,单我们实际使用时字段长度可能并没有到8(如’tong’这个数据字段)。变长字段的长度列表其实就是需要记录字段实际存储的长度。
注意,变长字段列表记录存储长度的顺序与我们变量声明的顺序是反过来的。比如我们声明两个Varchar字段的顺序是a(15),b(10),那么变长字段列表的存储的长度顺序就是10,15.
上面插入的第一条数据长度对应转为16进制如下。
因此变长长度列表存储的内容为060408.如下图。
3.2.2 Null值列表
Compact行格式会把Null值列统一管理起来。当然,如果表中不允许存放Null值,那么Null值列表就不存在了。
为什么要定义Null值列表呢?这是因为mysql数据库中定义的数据是对齐的。比如我们现在存储了四个数据(‘a,’Null,Null,‘b’),我们在存储完a以后再存储b,那么在查找数据的时候不是会造成混乱么?当然,我们也可以把Null值数据存储的空间提前预留出来,但是这无疑会浪费空间。因此,我们在记录信息之前开辟了一块空间来存储Null值的位置。而且这个实现也很简单,我们只需要一个bit位来存储是否字段是空。如果是空则置为1,否则为0。
上面第1条记录的Null值列表如下所示。同样,Null值列表的记录顺序与字段声明的顺序是相反的,并且col2声明是指定了Not Null非空约束,不会在Null值列表中进行记录。另外,如果声明时指定了主键,由于主键一定非空,因此也不会在声明列表中出现。
3.2.3 记录头信息
我们先创建一张数据表。
然后插入一些数据。重点关注下这些数据的记录头信息.
我们将上面的字段介绍如下。
delete_mask:该记录是否被删除。如果这个值是0,说明记录没有被删除,否则说明记录被删除。
由此可知记录删除是采用的逻辑删除,这是因为我们记录之间是紧密相连的,如果真正删除一个记录,将会需要导致后面的记录依次进行位移。所有被删除的记录会通过next_record构成一个垃圾链表,它们所占用的空间称为可重用空间。
min_rec_mask:存储目录项记录中主键值最小的目录项记录置为1,其它情况都置0.
Record_type:记录类型,0表示普通记录,1表示B+树的非叶子节点(目录页节点)、2表示最小记录,3表示最大记录。
heap_no:表示当前记录在本页中的位置。我们注意到前面图片的第一条记录的heap_no是2,那么0和1呢?实际上,mysql会自动创建两条虚拟记录,即最小记录和最大记录。位于记录链表的最前面位置。由于这两个记录不是我们创建的,因此并没有存储在用户空间中,而是放在Infimum和Supermun部分。他们其实是相当于头尾节点。
n_owned
:页目录中每个组的最后一条记录会存储该组的记录数,作为n_owned
字段。值的关注的是,在mysql中最小记录是一组,普通记录与其它记录是一组,因此最小记录中n_owned属性是1,最大记录的n_owned值是5.
next_record:它表示当前记录的真实数据到下一个记录的真实数据之间的偏移量。
介绍完以上知识点,我们来举一个例子,比如我们需要删除第2条记录,那么记录行格式会发生什么变化呢?删除第2条记录后的示意图如下。可以看到,首先第2条记录的delete_mask将标记为1,next_record标记为0。它前一个节点第一条记录的next_record会指向第3条记录。同时最大记录中的n_owned属性值变为4.
3.2.4 真实数据
真实数据里除了真实列还存储了三个隐藏列。
实际上,这几个列的真实名字是DB_ROW_ID,DB_TRX_ID,DB_ROW_PTR。
我们在上一篇文章介绍InnoDB索引的时候提到过,如果表中无主键,也没有适合做主键的(声明了唯一标识)其它列,会隐式的指定一个聚簇索引。实际上,在这种情况下就是会添加一个row_id的隐藏列。另外两个隐藏列与事务相关,我们会在之后介绍事务的博客中再进行介绍。
3.3 Compact行格式存储实例剖析
上面我们已经介绍了行格式,现在根据具体的实例进行下剖析。创建数据表并插入数据。
找到对应的mytest.idb
文件(注:推荐使用notepad++并安装使用Hex-Editor插件)
读出来的有效数据内容如下。我们采用不同颜色对于行格式的不同部分做了区分。
第一行中有03 02 01字段,这其实就是表中插入的第一条数据(‘a’,‘bb’,‘bb’,‘ccc’)的变长字段列表;
之后紧跟的00 是Null值列表,4个字段都非空的,因此这里使用的是00。倒数第二行中红色的06表示第三条插入数据中非空的数据的表示。因为'd',Null,Null,'fff'中四个数据分别用1,0表示是否为空的情况是0 1 1 0,倒过来仍然是0 1 1 0,转为16进制信息就是06.
之后的五个字节00 00 10 00 2c是记录头信息。其中2c就是next_record;
接下来的00 00 00 2b 68 00是隐藏主键DB_ROW_ID;
00 00 00 06 05是DB_TRX_ID;
接下来的7个字节80 00 00 00 32 01 10对应的回滚指针DB_ROW_PTR;
接下来的61对应真实数据a,62 62对应bb,62 62 20 20 20 20 20 20 20 20表示的是定长的bb,其中20表示没有真实数据,之后的63 63 63则表示ccc。