博客园  :: 首页  :: 新随笔  :: 订阅 订阅  :: 管理

MySQL存储引擎-InnoDB行格式

Posted on 2024-02-05 19:40  面具下的戏命师  阅读(85)  评论(0编辑  收藏  举报

MySQL存储引擎-InnoDB行格式

mysql作为一款主流的关系型数据库,是以记录为单位向表中插入数据的。目前为止,Innodb共支持COMPACT、 REDUNDANT、 DYNAMIC、和COMMPRESSED四种行格式。在MySQL5.7及以上版本,默认采用DYNAMIC格式。DYNAMIC与COMPACT格式基本一致,下文中我们会介绍区别。因其他类型日常工作中很少用到,本文作为读书笔记,不再进行介绍。

一、COMPACT行格式

我们首先创建一个示例表,如下:

create table record_format_demo (
  c1 varchar(10),
  c2 varchar(10) not null,
  c3 char(10),
  c4 varchar(10)
) charset = ascii row_format = compact;
insert into record_format_demo values('aaaa','bbb','cc','d');
insert into record_format_demo values('eeee','fff',NULL,NULL);

一条完整的记录被分为记录的额外信息和记录的真实数据两部分。

1.1、记录的额外信息

记录的额外信息分为3部分,分别是变长字段长度列表, NULL值列表,和记录头信息。

变长列表指的是varchar等不固定的长度类型,变长字段真实数据占用的字节数存储在记录的开头位置。形成一个变长字段长度列表,变长字段的真实数据所占用的字节数按照列的顺序逆序排放。例如示例表中的第一行数据。该示例表变长字段为c1、c2、c4列。因为我们使用的是ascii字符集,所以每个字符占用1字节。 因此:第一行数据的变长字段长度列表用十六进制表示为 01 03 04。 这个示例中的长度都可以用1字节表示,那如果变成字段的真实内容占用字节数很多,可能就用2字节来表示。InnoDB的规则是:如果变成字段允许存储的最大字节数小于255就用1个字节表示,如果大于255并且真实数据占用的字节数超过127字节就使用2字节来表示,不超过177就使用一个字节表示。 我们再看示例表的第二行,因为c4列为null。变成字段长度列表不存储NULL列的内容长度,因此第二行的变长列表为 03 04。

NULL值列表,在COMPACT中,把一条记录中为NULL的列统一管理,存储到NULL值列中。处理过程如下:首先统计允许存储NULL的列有哪些,例如示例表中的c1、c3、c4。如果表的所有列都不会NULL, 那NULL值列表就不存在了。如果有NULL值,就按照允许存储NULL的列对应一个二进制位,二进制为按照列的顺序逆序排列。二进制位为1时代表该列的值为NULL,为0时代表该列不为NULL。另外MySQL的NULL值列表必须用整数个字节表示,不足的在最高位补0。根据这个规则我们可以推算示例表中第一行数据的NULL值列表为 0 0 0 0 0 0 0 0 。第二行的NULL值列列表为 0 0 0 0 0 1 1 0。 转换为十六进制则为 00 和 06。

记录头信息,记录头信息固定5个字节,用于描述记录的一些属性。5个字节即40位,不同位代表不用的函数。二进制位代表的含义如下所示:

1、预留位  1位

2、预留位  1位

3、delete_flag  1位 标记是否删除

4、n_owned  4位  一个页面的记录会被分为若干组。每个组中有一个记录是大哥,其余为小弟。大哥的n_owned代表改组的记录条数,小弟的n_owned为0

5、heap_no  13位  表示当前记录在页面堆的相对位置

6、record_no  3位   表示记录类型。 0表示普通记录,1表示非叶结点记录,2表示Infimum记录, 3表示Supermum记录

7、next_record 16位  表示下一条记录的相对位置

1.2、记录的真实数据

MySQL的除了我们插入的真实数据之外,还会插入一些隐藏列。包括 rowid (6字节,在没有主键和非空唯一索引会创建)、trx_id (6字节,事务id)、roll_pointer (7字节,回滚指针)。根据以上我们的分析,我们通过hexdump指令来分析一下示例表的数据文件。结果如下:这里只截了一部分,即包含行数据的部分。

因此我们可以得到前两行的行记录为: 

说明 变长字段 NULL值列表 记录头信息 row_id trx_id roll_pointer c1列 c2列 c3列 c4列
第一行 01 03 04  00 00 00 10 00  2d 00 00 03 67 c5 78   00 00 13 82 3e 9e  a6 00 00 c0 2c 01 10   61 61 61 61   62 62 62 63 63 20 20 20 20 20 20 20 20   64
第二行  03 04  06 00 00 18 ff c2  00 00  03 67 c5 79 00 00 13 82 3e 9f a7 00 00 01 ca 01 10   65 65 65 65  66 66 66    

1.3、char列存储格式

在上述示例表中,我们采用的ascii字符集属于定长字符集即1个字节。如果使用变长字符集如utf8,utf8表示一个字符需要1~3个字节。针对变长字符集,虽然c3列的类型为char(10)。这一列的值占用的字节数也会被存储到变长字段长度列表中。执行如下sql修改:

alter table record_format_demo modify column c3 char(10) character set utf8;

那该列也会被加入变长列表:如图所示: 第一行的变长字段的长度列表变为了 01 0a 03 04

 1.4、溢出列

我们创建一个溢出列的测试表,如下所示:

create table off_page_demo(c varchar(65532)) charset=ascii row_format=compact;
insert into off_page_demo values(repeat('a', 65532));

上述表中,这个字符串大小为65532字节。我们知道MySQL一个数据页才16K,即16382字节,很显然1个数据页中存不了一条记录。 在COMPACT格式中:对于占用存储空间非常多的列,在记录的真实数据处只存储一部分数据,而把剩余的数据分散存储在其他页中。然后在记录真实数据处用20字节存储指向这些页的地址。(这20字节还包括分散在其他页中的数据所占的字节数)。 对于COMPACT格式,如果某些数据很多,则在本记录的真实数据处只存放768个字节和指向其他页的地址。 存储剩余数据的页叫做溢出页。

MySQL规定一个页至少存储2条记录。对于溢出页没有这个规定, 可以使用如下命令: hexdump  -C -v off_page_demo.ibd 。因图太大,这里我们分多张展示关键部分

根据图我们也可以计算出真是数据存储 a的数量是 768个。 再往后数20个字节存的是溢出页的地址(包括这些溢出页所占用的真实字节数),其他用于存放剩余数据的页用链表连接起来的。

变长字段 NULL值列表 记录头信息 row_id trx_id roll_pointer c1列
14 c3 00 00 00 10 ff f0 00 00 03 67 c5 7c 00 00  13 82 3e c3 be 00 00 80 3f 01 10 61 61......61  

二、其他格式

另外两个行格式,DYNAMIC格式和COMPRESSED格式。这另个行格式和COMPACT格式很像,只不过在处理溢出列时有些分歧。他们不会在记录真实数据处存储前768字节,而是把该列的所有真实数据全部存放到溢出页中。只在记录真实数据处存储20字节大小的指向溢出页的地址,COMPRESSED会采用压缩算法对页面进行压缩,基本用不到,就不再介绍了。

这里我们再验证一下DYNAMIC格式的溢出页。

关于DYNAMIC格式的普通页读者可自行验证。

三、行格式图展示