前言
了解行结构的意义:
- 知道设置好的主键可以节省空间
- 知道char的大小设置错误还不如varchar
- 知道字符集对变长字段类型的影响
- 知道null 和 空串的区别
- 还有一些…我还没发现
Compact行结构 一图以弊之
一、Innodb四种行格式
- COMPACT 紧凑型行格式
- REDUNDANT 字段长度偏移类型
- DYNAMIC 动态行格式
- COMPRESSED 压缩行格式
二、COMPACT行格式示意图
三、变长字段长度列表
1.目的
针对于变长字段列入VARCHAR
,VARBINARY
,TEXT
,BLOB
等变长字段类型,由于存储的字段长度不固定,如果不记录该字段的真实长度,那么就无法计算出这一行的准确长度,那么就无法进行行与行的切分。
所以变长字段会占用两部分空间,第一部分是数据的长度,另一部分是该数据所占用的字节数。
2.规则
- 变长字段长度列表是按照
逆序存放
的,(这是跟页读取有关,后面补充) - 变长字段的值为null时,不显示在变长字段长度列表中
- 变长字段的实际字节长度小于127时,用一个字节表示。 大于127时,用两个字节表示,最多也只需要用2个字节(这个跟内存页的规定有关,这里暂不拓展)
- 一个字节8个比特位,最高位为0时,表示这个字段占一个字节,2^7-1 = 127。如果是两个字节,那么第一个字节的最高位应该为1,这样才会读取第二个字节跟第一个字节进行组合表示。
3.逆序读取变长字段长度列表
在经过2次尝试后,发现变长字段长度列表是从后往前读的。虽然知道字段的摆放顺序是逆序的,但是对于读取的顺序是逆序的很多文档和书籍中并没有提到。
16进制变长字段长度列表为: 03 0e 81
先读取81,发现最高位为1,那么表示这是一个双字节表示一个字段的实际长度,接着读取0e,将81的最高位改为0后,组成的双字节数字就是该字段的实际长度270
(03) 这里首位字符为0表示单字节表示一个字段的实际长度,字段实际长度为3
4.字符与字节之间的计算
变长字段长度列表存储的是该字段字节的长度,而我们在定义的时候一般是定义的字符的长度,那么如何计算呢?
变长字段最大字节数计算公式:
最大字节数 = 字段最大字符数*字符集单个字符最大字节数
这里有几个定义需要解释一下
字段最大字符数是指的在你定义varchar(M)时,这个M就是你能存储的最大字符数
字符集单个字符最大字节数指的是该字符集最多用几个字节来表示一个字符。
最常见的utf8mb4字符集就是用1~4个字节来表示一个字符。
四、null值列表
1.目的
null值列表就是告知在字段中,哪些可能为null的值是null,哪些不是null。这样就能按照顺序正确的读取字段的值。
2.规则
- 只有字段值可为null的字段才能显示在null值列表中
- null值列表是通过bit位来进行标识的,一个字段占一个比特位
- null值列表bit位是按照字段逆序排列的
- 当字段的比特位不够8的倍数时,需要在高位补0
- 字段值为null的bit位为1,否则为0
五、固定头信息
1.目的
记录行的位置,页信息,记录类型等信息,是固定的五个字节的大小
2.头信息的组成
名称 | 大小(bit位) | 描述 |
---|---|---|
预留位 | 1 | 没有使用 |
预留位 | 1 | 没有使用 |
delete_flag | 1 | 删除标识 |
min_rec_flag | 1 | B+树每层非叶子节点中最小的目录项记录都会添加该标记(涉及到索引,后面会写) |
n_owned | 4 | 一个页有多条记录,这些记录会再进行分组,每个分组有个老大,老大回存储这个分组中有几条记录,这个是给老大用的 |
heap_no | 13 | 当前记录在页中相对位置 |
record_type | 3 | 记录类型 0表示普通记录 1表示B+树非叶子节点的目录项记录 2表示Infimum记录 3表示Supremum记录 |
next_record | 16 | 下调记录的相对位置 |
以上表格中刚好可以组成一个5字节的头信息数据
六、额外信息
1.作用
这个信息是mysql自动生成的信息,主要包括row_id 行号 ,trx_id事务id ,roll_pointer 回滚指针。因为mysql是聚簇索引,所有的记录其实就是一颗索引树,所有的记录在树的最底层的叶子节点上。如果我们没有设置主键,也没有一个不存在null值的Unique键,那么mysql会为我们创建一个row_id来作为主键。
名称 | 大小(字节数) | 是否必须 | 描述 |
---|---|---|---|
row_id | 6 | 否 | 行号,当没有主键也没有不为null的Unique键时才会生成 |
trx_id | 6 | 是 | 事务ID |
roll_pointer | 6 | 是 | 回滚指针 |
七、真实记录
在额外信息之后,就是字段的真实数据,按照顺序排放,null值字段被省略(通过null值列表进行组合成完整的字段记录数据)
八、验证
1.基于最简单的ASCII测试与解析行格式
# 创建数据库
create database mysqlstudy;
# 创建基础表
create table compact_record_demo(
col1 varchar(10),
col2 char(5),
col3 int,
col4 varchar(5),
col5 varchar(15) not null
) charset=ascii row_format=COMPACT;
# 插入数据
insert into compact_record_demo (col1,col2,col3,col4,col5) values ('aa','bbb',4,null,'cccc');
insert into compact_record_demo (col1,col2,col3,col4,col5) values ('0',null,5,null,'cccc');
2.获取数据文件
然后去/var/lib/mysql/mysqlstudy
目录下去取出数据文件 compact_record_demo.ibd
文件
3.找到行记录所在位置
用Sublime Text
打开ibd文件, 根据ASCII对照找到a所对应的16进制值61 。然后再Sublime Text中搜索
这一块就是真实数据内容了
那我们开始解析第一条行记录
4.变长字段长度列表计算流程:
col1 长度是2,对应的十六进制就是0x02 ,计入变长字段列表
col2 长度是3,对应十六进制就是0x03,但是是定长字符类型,所以不计入变长字段列表
col3 定长数字 不计入变长字段长度列表
col4 值为null ,不计入变长字段长度列表,计入null值列表
col5 长度为4,对应十六进制值为0x04,计入变长字段列表
那么变长字段列表的16进制展示形式为 0x04 0x02,而这条记录的尾部为’cccc’,十六进制展现形式为63636363,那么根据一头一尾,我们可以确定整条行记录的信息为一下的内容
5.compact_record_demo的第一条记录解析:
0402 // 变长字段列表
08 // null值列表
00 00 10 00 2a //固定头信息 5个字节
00 00 00 00 24 00 // 没有主键时,固定6个字节的rowId,作为主键
00 00 00 26 5a c8 // 事务ID 固定6个字节
ba 00 00 01 f6 01 10 // 回滚指针 固定7个字节
61 61 // 字段col1
62 62 62 20 20 // 字段col2 值为 bbb ,由于是定长char,所以会在后面补20空格
80 00 00 04 // 字段col3 值为4(整型数据表示法会在后面进行补充
)
63 63 63 63 // 字段5 值为cccc
6.null值列表计算
col1 col2 col3 col4 都可以为null ,所以需要四个bit位来展示,那么会占用一个字节,所以08表示null值列表
二进制表现形式:00001000
基于前面null值列表的规则,可以计算出每个字段对应的位置和对应位置bit位的值
九、小结
在学习的过程中,只有不断理解,不断验证,最后进行归纳总结,才能转化为自己的东西。其中可能有些谬误之处,希望不会对后来者造成坏的影响。我所使用的mysql版本是5.7。