开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
InnoDB数据行是更为底层的东西,在宏观层面我们知道InnoDB是Mysql5.5之后默认的存储引擎,采用B+tree作为默认索引,当执行一条 select 查询语句时,Mysql期间需要经过建立连接、查询缓存、词法分析、语法分析、查询优化、执行计划等过程,最后从存储引擎中根据B+tree索引获得数据。
但是微观层面,数据在存储引擎中是以什么样的形式存在也是值得我们去研究的。
我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式也被称为行格式或者记录格式 。 InnoDB 存储引擎到现在为止有4种不同类型的行格式 ,分别是 Compact 、 Redundant 、Dynamic 和 Compressed 行格式。其中Compact是最常用的,我们主要介绍这种行格式。
设置行格式
在数据库中,我们可以在创建或修改表的语句中指定行格式
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
ALTER TABLE 表名 ROW_FORMAT=行格式名称
mysql> create table compact_test(
-> a1 varchar(10),
-> a2 varchar(10) not null,
-> a3 char(10),
-> a4 varchar(10)
-> )charset=ascii row_format=compact;
Query OK, 0 rows affected (0.51 sec)
向表中插入数据
INSERT INTO compact_test (a1, a2, a3, a4)
VALUES('aaaa', 'bbb', 'cc', 'd'),
('eeee', 'fff', NULL, NULL);
COMPACT 行格式长什么样?
可以看到,一条完整的记录分为「记录的额外信息」和「记录的真实数据」两个部分,其中记录的真实数据中又分为隐藏列和用户真实记录数据。
记录的额外信息
记录的额外信息包含 3 个部分:变长字段长度列表、NULL 值列表、记录头信息。
变长字段长度列表
MySQL 支持一些变长的数据类型,比如 VARCHAR(M) 、 VARBINARY(M) 、各种 TEXT 类型,各种 BLOB 类型,我们也可以把拥有这些数据类型的列称为变长字段,变长字段中存储多少字节的数据是不固定的,所以我们在存储真实数据的时候需要顺便把这些数据占用的字节数也存起来,这样读取数据的时候才能根据这个变长字段长度列表去读取对应长度的数据。
在 Compact 行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放。
现在表中有两条数据,我们看看这两行条记录的行格式中的变长字段长度列表是怎样存储的。
第一条记录中变长column:
| 列名 | 存储内容 | 内容长度 |
|---|---|---|
| a1 | aaaa | 4 |
| a2 | bbb | 3 |
| a4 | d | 1 |
第二条记录中变长column:
| 列名 | 存储内容 | 内容长度 |
|---|---|---|
| a1 | eeee | 4 |
| a2 | fff | 3 |
另外需要注意的一点是,变长字段长度列表中只存储值为 非NULL 的列内容占用的长度,值为 NULL 的列的长度是不储存的 。也就是说对于第二条记录来说,因为 a4 列的值为 NULL ,所以第二条记录的变长字段长度列表 只需要存储 a1 和 a2 列的长度即可。
NULL值列表
表中的某些列可能会存储 NULL 值,如果把这些 NULL 值都放到记录的真实数据中会比较浪费空间,所以 Compact 行格式把这些值为 NULL 的列存储到 NULL值列表中。
如果表中没有允许存储 NULL 的列,则 NULL值列表 也不存在了,否则将每个允许存储 NULL 的列对应一个二进制位,二进制位按照列的顺序逆序排列,二进制位表示的意义如下:
- 二进制位的值为
1时,代表该列的值为NULL。 - 二进制位的值为
0时,代表该列的值不为NULL。
接下来,我们看看上面两条记录的行格式中的 NULL 值列表是怎样存储的。
MySQL 规定 NULL值列表 必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补 0 。
第一条记录
所以第一条记录的 NULL值列表 用十六进制表示就是: 0x00 。
第二条记录
所以第二条记录的 NULL值列表 用十六进制表示就是: 0x06 。
所以这两条记录在填充了 NULL值列表 后的行格式就是这样:
记录头信息
除了变长字段长度列表 、NULL值列表 之外,还有一个用于描述记录的记录头信息 ,它是由固定的 5 个字节组成。 5 个字节也就是 40 个二进制位,不同的位代表不同的意思,这些二进制位代表的详细信息如下表:
| 名称 | 大小(bit) | 描述 |
|---|---|---|
| 预留位1 | 1 | 没有使用 |
| 预留位2 | 1 | 没有使用 |
| delete_mask | 1 | 标记该记录是否被删除 |
| min_rec_mask | 1 | B+树的每层非叶子节点中的最小记录都会添加该标记 |
| n_owned | 4 | 表示当前记录拥有的记录数 |
| heap_no | 13 | 表示当前记录在记录堆的位置信息 |
| record_type | 3 | 表示当前记录的类型, 0 表示普通记录, 1 表示B+树非叶子节点记录, 2 表示最小记录, 3表示最大记录 |
| next_record | 16 | 表示下一条记录的相对位置 |
记录头信息中包含的内容很多,这里只重点讲一下重点几个:
- delete_mask :标识此条数据是否被删除。从这里可以知道,我们执行 detele 删除记录的时候,并不会真正的删除记录,只是将这个记录的 delete_mask 标记为 1。
- next_record:下一条记录的位置。从这里可以知道,记录与记录之间是通过链表组织的。在前面我也提到了,指向的是下一条记录的「记录头信息」和「真实数据」之间的位置,这样的好处是向左读就是记录头信息,向右读就是真实数据,比较方便。
- record_type:表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录
记录的真实数据
记录真实数据部分除了我们定义的字段,MySQL 会为每个记录默认的添加一些列(也称为 隐藏列 ),具体的列如下:
| 列名 | 是否必须 | 占用空间(bit) | 描述 |
|---|---|---|---|
| row_id | 否 | 6 | 行id,唯一标识一条记录 |
| transaction_id | 是 | 6 | 事务id |
| roll_pointer | 是 | 7 | 回滚指针 |
优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个 Unique 键作为主键,如果表中连 Unique 键都没有定义的话,则 InnoDB 会为表默认添加一个名为row_id 的隐藏列作为主键。所以我们从上表中可以看出:InnoDB存储引擎会为每条记录都添加 transaction_id和 roll_pointer 这两个列,但是 row_id 是可选的(在没有自定义主键以及Unique键的情况下才会添加该列)。这些隐藏列的值不用我们操心, InnoDB 存储引擎会自己帮我们生成的。
总结
至此,InnoDB的compact数据行格式全部介绍完了。
- 变长字段是如何存储的?
- NULL值是如何存储的?
- 记录的真实数据和额外数据分别是什么?
- compact隐藏列包括哪些?