数据页结构
mysql默认每个数据页为16KB,InnoDB引擎的Compact行记录结构由以下字段组成:
变长字段长度列表 | null值列表 | 记录头信息 | 列数据1 | 列数据N |
---|---|---|---|---|
不定长 | 不定长 | 5字节 |
变长字段长度列表
对于非固定长度的字段类型,例如varchar
、text
、blob
、多字节编码的char
等,通过变长字段长度列表记录当前行记录的对应字段值的长度(列顺序倒序存放)。
列名 | 值 | 值长度(10进制) | 值长度(16进制) |
---|---|---|---|
a | 'aaa' | 3 | 0x03 |
b | 'b' | 1 | 0x01 |
c | 'ccccc' | 5 | 0x05 |
则示例的变长字段长度列表的值为05 01 03
。
null值列表
如果记录的列允许为null,则在该值中记录当前行中值为null的字段(列顺序倒序存放)。
列名 | 允许为null | 是否为null | 值(2进制) |
---|---|---|---|
a | true | true | 1 |
b | false | - | - |
c | true | true | 1 |
d | true | false | 0 |
则示例的null值列表的值为011
(二进制),又由于该值必须为整数字节,所以高位补0后的值为00000011
即0x03
。如果表中允许为null的列超过8个,则null值列表的值长度大于1字节。
记录头信息
记录头占固定长度5字节:
名称 | 大小(bit) | 描述 |
---|---|---|
() | 1 | 未知 |
() | 1 | 未知 |
deleted_flag | 1 | 该行是否已被删除 |
min_rec_flag | 1 | 为1,如果该记录是预先定义为最小的记录 |
n_owned | 4 | 该记录拥有的记录数 |
heap_no | 13 | 索引堆中该条记录的排序记录 |
record_tye | 3 | 记录类型,000表示普通,001表示B+树节点指针,010表示Infimum,011表示Supremum,1xx表示保留 |
next_record | 16 | 页中下一条记录的相对位置 |
案例
CREATE TABLE `user_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(11) NOT NULL,
`age` int(11) DEFAULT NULL,
`address` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into user_info (id,name,age,address) values (1,'tom',18,'shanghai');
查看mysq目录下的文件user_info.ibd
,通过命令hexdump -Cv user_info.ibd > user_info.txt
或者使用软件例如UltraEdit
查看二进制数据。
普通记录
0x0000C078
位置
08 变长字段address的长度为8
03 变长字段name的长度为3
00 null值字段列表
00 00 10 FF F0 记录头信息(5字节)
80 00 00 01 RowID:主键id的值为1
00 00 00 1D 4B 93 事务ID(6字节)
E7 00 00 80 31 01 10 回滚指针(7字节)
74 6F 6D 字段name的值:tom
80 00 00 12 字段age的值:18
73 68 61 6E 67 68 61 69 字段address的值:shanghai
最大/最小记录
数据页的每行数据以单向链表的形式连接,因此需要有链表头和链表尾。最小记录就是该单向链表的链表头,最大记录则是该单向记录的链表尾,且两者的记录位置固定不变。
最小记录的位置为0x0000C063
,其值固定为infimum
。
01 00 02 00 1D 记录头信息(5字节)
69 6E 66 69 6D 75 6D 最小记录(固定值infimum)
最大记录的位置为0x0000C070
,其值固定为supremum
。
02 00 0B 00 00 记录头信息(5字节)
73 75 70 72 65 6D 75 6D 最大记录(固定值supremum)
Page Directory
Page Directory
中存放了当前数据页中部分数据的相对位置,即
Page Directory
是当前数据页数据的目录,mysql通过Page Directory
使用二分法减少查询时间。
Page Directory
中每个相对位置占2个字节长度,且相对位置从0x0000FFF7
倒序排序。其中至少有最小记录和最大记录的相对位置,如下图:
00 63
表示 0x0000C063
,00 70
表示 0x0000C070
。
当表行数据较多时(创建了35行数据),Page Directory
会每4~8条记录存一个相对位置,例如下图:
00 63
-> 0x0000C063
-> 最小记录
00 F8
-> 0x0000C0F8
-> ID为4的记录
01 98
-> 0x0000C198
-> ID为8的记录
02 38
-> 0x0000C238
-> ID为12的记录
02 D8
-> 0x0000C2D8
-> ID为16的记录
03 78
-> 0x0000C378
-> ID为20的记录
04 18
-> 0x0000C418
-> ID为24的记录
04 B8
-> 0x0000C4B8
-> ID为28的记录
00 70
-> 0x0000C070
-> 最大记录
数据链表
数据页中行记录按照主键值由小到大顺序串联成一个单链表,且单链表的链表头为最小记录,链表尾为最大记录。并且为了更快速地定位到指定的行记录,通过Page Directory
实现目录的功能,借助Page Directory
使用二分法快速找到需要查找的行记录。
删除表(为了清空文件防止删除的记录干扰)后重新创建表和数据:
CREATE TABLE `user_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(11) NOT NULL,
`age` int(11) DEFAULT NULL,
`address` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into user_info (id,name,age,address) values (3,'tom',18,'shanghai');
insert into user_info (id,name,age,address) values (1,'peter',22,null);
insert into user_info (id,name,age,address) values (2,'wsen',21,'beijing');
最小记录:
01 00 02 00 44
69 6E 66 69 6D 75 6D 最小记录(固定值infimum)
最大记录:
04 00 0B 00 00
73 75 70 72 65 6D 75 6D 最大记录(固定值supremum)
ID为3的记录:
08 变长字段address的长度为8
03 变长字段name的长度为3
00 null值字段列表
00 00 10 FF F0 记录头信息(5字节)
80 00 00 03 RowID:主键id的值为3
00 00 00 1D 4B 80
DB 00 00 80 2E 01 10
74 6F 6D 字段name的值:tom
80 00 00 12 字段age的值:18
73 68 61 6E 67 68 61 69 字段address的值:shanghai
ID为1的记录:
05 变长字段name的长度为5
02 null值字段列表:0x02 -> 00000010即address为null
00 00 18 00 22 记录头信息(5字节)
80 00 00 01 RowID:主键id的值为1
00 00 00 1D 4B 81
DC 00 00 80 1B 01 10
70 65 74 65 72 字段name的值:peter
80 00 00 16 字段age的值:22
字段address的值:null
ID为2的记录:
07 变长字段address的长度为7
04 变长字段name的长度为4
00 null值字段列表
00 00 20 FF B7 记录头信息(5字节)
80 00 00 02 RowID:主键id的值为2
00 00 00 1D 4B 86
DF 00 00 80 2F 01 10
77 73 65 6E 字段name的值:wsen
80 00 00 15 字段age的值:21
62 65 69 6A 69 6E 67 字段address的值:beijing
Page Directory
最小记录的记录头信息最后2字节00 44
-> 0x0000C063
偏移0x0044
-> 0x0000C0A7
,即ID为1的记录的id位置;
ID为1的记录的记录头信息最后2字节00 22
-> 0x0000C0A7
偏移0x0022
-> 0x0000C0C9
,即ID为2的记录的id位置;
ID为2的记录的记录头信息最后2字节FF B7
-> 0x0000C0C9
偏移0xFFB7
-> 0x0000C080
,即ID为3的记录的id位置;
ID为3的记录的记录头信息最后2字节FF F0
-> 0x0000C080
偏移0xFFF0
-> 0x0000C070
,即最大记录的记录位置;