前言:个人笔记记录!!! 个人笔记记录!!! 个人笔记记录!!!
1.环境搭建
MySQL:8.0.29,VS Code,Hex Editor插件
1.1 数据准备
创建完对应数据库后,获取对应.idb文件
CREATE TABLE `test_01` (
`a` int NOT NULL AUTO_INCREMENT,
`b` varchar(5) COLLATE utf8mb4_general_ci NOT NULL,
`c` char(5) COLLATE utf8mb4_general_ci DEFAULT NULL,
`d` varchar(5) COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
INSERT INTO `test_01` VALUES (1, '清和三十', '你', '他');
INSERT INTO `test_01` VALUES (2, '哈哈', NULL, NULL);
2. 理论分析
MySQL可以使用多种不同的格式保存记录:Compact
,Redundant
,Dynamic
,Compressed
;
2.1 Compact
先上格式图:
变长长度列表
存储的是变长字段占用的字节数(在变长字符集中,char类型的也会放入
),逆序存放(指向记录中间,向左为记录额外信息,向右为真实数据)。目前只有b
,d
列是变长字段,所以变长字段列表会保存这两列的占用字节数。如果某列占用的字节数比较多,该列需要用2个字节来表示。列最大字节数计算方式: 字符集最大字节x创建该列时指定的长度
。通过上述计算出的字节数<=255,则使用1字节表示,就算大于255还是会判断实际占用字节数的,实际占用字节数<=127,使用1字节,否则2字节。
如果存在数据页溢出,则只存放本页的数据长度。该列表并不会记录值为NULL的列所占用的字节数
NULL值列表
如果表中没有允许存储 NULL 的列,则 NULL值列表 就为0。在本例中只有c
,d
列允许存储NULL值。每个NULL列占用一个二进制位,逆序存放。该处必须用整数个字节,不足高位补0
记录头信息
描述记录的额外信息,占用固定的5字节。这里就从别人那偷张图了😣
真实数据
在上面的格式图中,并没有画出隐藏列。除了我们自己添加的列外,MySQL还会为我们添加一些隐藏列:
列名 | 占用字节 | 描述 |
---|---|---|
row_id | 6 | 唯一标识一条记录,非必须 |
transaction_id | 6 | 事务ID |
roll_pointer | 7 | 回滚指针 |
如果没有主键的话,就会表中UNIQUE列做为主键,这也没有,便会使用row_id
。
杂谈
在变长字符集中,CHAR(M)存储的范围至少是M
个字节,防止后续更新需要重新分配空间。这对变长字段列表也是一样,就算只存了一个字符,在变长字段列表处用的长度还是M
3. 看见数据
上面理论分析完了,接下来就该实战了。VS Code
打开.idb
文件,都是一堆16进制数据,可以验证下我们上面的理论。
忽略掉一些无用数据,先定位到变长字段列表
处。根据上面的解析,得出第一条记录的变长字段列表应该由b
,c
,d
这三列组成。b
列4个字符,而字符集为utf8mb4
,中文占3字节,得出b
列占用12字节。 C
列一个字符,但由于是可变字符集,同时还是char(M)
类型,则实际字节数为5字节。 d
列和b
列计算方式一样,占用3字节。 这三个数都可以用1字节存储:12
5
3
,逆序得3
5
12
。如图
将这些长度转为
16进制
,正好是0x03
,0x05
,0x0C
。
接下来就是NULL值列表了,第一条记录没有NULL
值,所以占一字节都是0就行了,如上图0c
后面跟的就是1字节的NULL值列表
。
直接跳过5字节的记录头信息,来到真实数据处:
由于我们已经有了主键,row_id
就没有存在的必要了。但是这里有个细节:虽然row_id
没有存在的必要了,但是MySQL
会将我们自定义的id
放在row_id
的位置,也就是说先往后数4字节就是自定义的主键id值,再往后数6字节就是transaction_id
的值,再往后数7字节就是roll_pointer
内容。后面的就是表中其他列的数据了。
ps:至于为什么主键值是0x80 00 00 01,而不是0x00 00 00 01,这是因为MySQL规定有符号数高位为1
从0xE6
开始就是第二列的数据了,也就是b
列的值,utf8mb4
中一个中文占3字节,4个字符也就是12字节,后面的列以此类推。虽然c
列只有一个字符,但还是占用了5字节,后面补了两个0x20
。
第二条记录类似,这里就说下NULL
值列表吧
从0x06
开始便是第二条记录,只有一列不为NULL
的可变列,所以可变字段长度列表是0x06
。由于我们有两列允许为NULL
且为NULL
(b,c
列),就有两个标志位为1,再逆序存放就是0000 0011
,转16进制就是0x03
。后面的和第一条记录都是一样的逻辑啦。
4. 其他格式
Redundant
属于比较老的一种了,它是用字段长度偏移量来计算的,了解即可。Dynamic
就是Compact
溢出列的情况下,不保存部分数据,而是将所有数据存放在其他页面中。
5. 闲聊
MySQL
中一条记录最多65535个字节(不包括隐藏列和记录头信息),一条记录由真实数据,真实数据占用长度,NULL
值标识(可以没有
),除去2字节的数据长度列表(最坏的打算
),NULL
值标识2字节,那就只剩下65531字节了,假设使用utf8mb4
,只有单列,则varchar(m)
中m
最大只能在16382
左右(前面两个最坏打算不精确),但让这只有一个列的情况下,还应该加上其他列
一个页16k
,就是16384
字节,一列最多65535
字节,这会造成溢出,在compact
中,记录的真实数据的地方会只保存一部分数据,然后用20字节存放溢出数据所在页的地址。发生行溢出公式:132+2x(27+n)<16384
,132
为页中其他内容占用字节,27
为记录中的额外信息,2
为MySQL
规定一页中最少拥有2条记录,解出n<8099,这只是在只有一个列的情况下。