InnoDB 存储引擎行格式 COMPACT
前言
InnoDB 存储引擎包含 4 种行格式,分别为 Compact、Redundant、Dynamic、Compressed,其中 Compact、Dynamic、Compressed 行格式基本相同,在 Mysql5.7 版本中默认的行格式就是 Dynamic,那么这三种行格式到底如何存储呢?这就需要用到之前搭建环境的知识,具体信息可以参考InnoDB行格式环境搭建
简单概括就是新建了一个 test 数据库,同时新建了一个表 record_format_demo,建表语句如下
### 执行建表语句 创建测试表record_format_demo
mysql> CREATE TABLE record_format_demo (
-> c1 VARCHAR(10),
-> c2 VARCHAR(10) NOT NULL,
-> c3 CHAR(10),
-> c4 VARCHAR(10)
-> )CHARSET=ascii ROW_FORMAT=COMPACT;
创建表后往数据库中插入两行数据,如下所示
+------+-----+------+------+
| c1 | c2 | c3 | c4 |
+------+-----+------+------+
| aaaa | bbb | cc | d |
| eeee | fff | NULL | NULL |
+------+-----+------+------+
分析测试表 record_format_demo 的 ibd 文件可以摘取部分内容得到如下 16 进制数据
0000c070 73 75 70 72 65 6d 75 6d 01 03 04 00 00 00 10 00 |supremum........|
0000c080 2d 00 00 00 00 02 02 00 00 00 00 8f 1f b7 00 00 |-...............|
0000c090 01 2b 01 10 61 61 61 61 62 62 62 63 63 20 20 20 |.+..aaaabbbcc |
0000c0a0 20 20 20 20 20 64 03 04 06 00 00 18 ff c2 00 00 | d..........|
0000c0b0 00 00 02 03 00 00 00 00 8f 1f b7 00 00 01 2b 01 |..............+.|
0000c0c0 1e 65 65 65 65 66 66 66 00 00 00 00 00 00 00 00 |.eeeefff........|
分析 COMPACT 行结构
COMPACT 行结构简单概括如下所示
上图表明 COMPACT 行格式代表了一条记录将被分为两部分记录的额外信息和记录的真实信息,其中额外信息又被分为三大块分别为:变长字段长度列表、NULL 值列表和记录头信息,这些都是什么东西呢?具体分析如下
记录的额外信息
变长字段长度列表
分析这部分结构首先要知道什么是变长字段,简单定义就是存储字段的长度并不固定,我们用的最多的代表就是 VARCHAR 类型,其次还有 TEXT、BLOB、VARBINARY 等,如果表数据中不包含变长字段那么这部分不需要有。
对于上面的测试表 record_format_demo 表第一列值而言那就是 c1、c2、c4,也就是说变长字段长度列表中存放的就是这三个列的实际长度,因为这个测试表采用的是 ascii 编码,也就是说一个字节编码一个字符,所以这三列占用空间分别为
变长字段 | 字段值 | 内容长度(10 进制) | 内容长度(16 进制) |
c1 | aaaa | 4 | 0x04 |
c2 | bbb | 3 | 0x03 |
c4 | d | 1 | 0x01 |
所以变长列表显示如下
04 03 01
但真实结果真是这样吗,显然在上面的 16 进制中没有找到类似排序,因为这些变长列表长度需要按照列的逆序排列如下所示
01 04 03
这就对应 ibd 文件中截取部分
需要注意的是,变长字段长度列表统计的是非 NULL 的列值长度,也就是测试表的第二列变长字段长度列表只有两个 c1 和 c2,也就是 0x04 和 0x03,展示结果如下
03 04
对应 ibd 文件截取部分
NULL 值列表
在一个表中一般会设置一个列是否为 NULL,因为如果将 NULL 值直接存储到真实数据区将十分消耗内存,所以需要将这些为 NULL 的列统一管理。
首先我们需要知道,这个 NULL 值列表的统计范围,这个其实很好理解只要有可能为 NULL 的列才可能被统计到所以主键列和设置了 NOT NULL 的列将不会被统计,测试表 record_format_demo 就只有 c1、c3、c4 才能被统计。
那么我们将如何统计呢?就是将允许为 NULL 的列对应一个二进制位
- 当该列值为 NULL,那么二进制位为 1。
- 当该列值不为 NULL,那么二进制位为 0。
需要注意的是二进制位的排放顺序是列的逆序排列。
综上第一列值的 NULL 值列表如下
第二列值的 NULL 值列表如下
这两个值对应的 ibd 文件位置如下
记录头信息
这部分由固定的 5 个字节组成,包含一些是否删除的标识、记录堆的位置信息、下一条记录的相对位置等等。
记录头信息在 ibd 文件位置如下所示
记录的真实数据
这一部分并不全是记录的真实数据,还有一些隐藏列如下
列名 | 是否必须 | 占用字节数 | 描述 |
DB_ROW_ID | 否 | 6 字节 | 行 ID,唯一标识一条记录 |
DB_TRX_ID | 是 | 6 字节 | 事务 ID |
DB_ROLL_PTR | 是 | 7 字节 | 事务回滚指针 |
注意:DB_ROW_ID 之所以不是必须是因为如果用户自己定义主键,那么优先使用用户定义的主键 id,如果用户没有定义主键会选取 Unique 列为主键,如果没有定义 Unique 列,这才会使用 DB_ROW_ID 作为行 ID,标识唯一记录。
测试表的第一行测试数据,隐藏列 ibd 文件展示如下
对于后面非隐藏列展示对照如下
aaaa ------> 61 61 61 61
bbb ----- > 62 62 62
cc --------> 63 63 20 20 20 20 20 20 20 20(定长字段需要填充 0x20)
d ---------> 64