导读:
本文是对上一篇文章的延续,在阅读本文之前建议先快速阅读一下《InnoDB Page结构详解》
创建表结构,插入三条数据
Root@mysqldb 11:01: [xucl]> show create table t\G
*************************** 1. row ***************************
Table: t
Create Table: CREATE TABLE `t` (
`id` int(11) NOT AUTO_INCREMENT,
`c1` varchar(10) DEFAULT ,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
root@mysqldb 11:01: [xucl]> select * from t;
---- ------
| id | c1 |
---- ------
| 1 | a |
| 2 | |
| 3 | a |
---- ------
3 rows in set (0.00 sec)
用ue打开t.ibd文件,为什么用ue呢,我觉得ue是观察16进制文件最好的工具了,hexdump由于大小端的原因,看起来太费劲了。
page no=3是第一个数据页,那么我们从0000c000到00010000之间就是我们这个数据页所有的数据了。
整理一下得到如下:
0 1 2 3 4 5 6 7 8 9 a b c d e f
c000 38 AD C1 F5 00 00 00 03 FF FF FF FF FF FF FF FF
c010 00 00 00 11 4E 07 B2 CA 45 BF 00 00 00 00 00 00
c020 00 00 00 00 04 D2 00 02 00 C1 80 05 00 00 00 00
c030 00 AF 00 02 00 02 00 03 00 00 00 00 00 00 00 00
c040 00 00 00 00 00 00 00 00 08 48 00 00 04 D2 00 00
c050 00 02 00 F2 00 00 04 D2 00 00 00 02 00 32 01 00
c060 02 00 1C 69 6E 66 69 6D 75 6D 00 02 00 0B 00 00
c070 73 75 70 72 65 6D 75 6D 01 00 00 00 10 00 18 80
c080 00 00 01 00 00 00 0C 81 E7 B5 00 00 00 05 01 10
c090 61 01 00 00 18 00 18 80 00 00 02 00 00 00 0C 81
c0a0 E8 B6 00 00 00 05 01 10 01 00 00 00 20 FF C1 80
c0b0 00 00 03 00 00 00 0C 81 E9 B7 00 00 00 05 01 10
c0c0 61
................................
fff0 00 00 00 00 00 70 00 63 38 AD C1 F5 4E 07 B2 CA
先看File Header 38字节
38 AD C1 F5:4字节,表示数据页的checksum值
00 00 00 03:4字节,表示页的偏移量,这个页也就是page no = 3的页
FF FF FF FF:4字节,前一个页的指针,因为我们现在只有一个page,所以为FF FF FF FF
FF FF FF FF:4字节,下一个页的指针,因为我们现在只有一个page,所以为FF FF FF FF
00 00 00 11 4E 07 B2 CA:8字节,page的LSN
45 BF:2字节,页的类型,表示这个page是数据页
00 00 00 00 00 00 00 00:8字节,独立表空间该值都为0
00 00 04 D2:4字节,表示SPACE ID,表空间ID可以在informa shema下的INNODBSYSTABLESPACES表查到
再看Page Header 56字节
PAGENDIR_SLOTS(2字节):00 02表示只有2个slot,每个slot占用2个字节,结束偏移量为0ff7,那么开始偏移量就为0ff4,也就是00 70 00 63,倒序存储
PAGEHEAPTOP(2字节):00 C1,0000c000 c1 = c0c1,即最后一条记录往后一个位置
PAGENHEAP(2字节):80 05,表示page内共有5条记录,?
PAGE_FREE(2字节):00 00,表示可重用空间首地址
PAGE_GARBBAGE(2字节):00 00:表示删除的记录字节数
PAGELASTINSERT(2字节):00 AF:最后插入位置,C000 00AF= C0AF
PAGEDIRECTION(2字节):00 02:表示PAGENO_DIRECTION
PAGENDIRECTION(2字节):00 02,一个方向连续插入记录的数量
PAGENRECS(2字节):00 03,页中包含的记录数,注意不包含最大最小记录
PAGEMAXTRX_ID(8字节):00 00 00 00 00 00 00 00,修改当前页的最大事务ID,注意该值仅在Secondary Index中定义
PAGE_LEVEL(2字节):00 00,B树的高度
PAGEINDEXID(8字节):00 00 00 00 00 00 08 48,索引ID,该值可以在informationschema下的INNODBSYS_INDEXES查到
PAGEBTRSEG_LEAF(10字节):00 00 04 D2 00 00 00 02 00 F2:B 树数据页非叶节点所在段的segment header。注意该值仅在B 数的Root页中定义
PAGEBTRSEG_TOP(10字节):00 00 04 D2 00 00 00 02 00 32:B 树数据页所在段的segment header。注意该值仅在B 数的Root页中定义
最大最小虚拟记录:
infimum:01 00 02 00 1C 69 6E 66 69 6D 75 6D 00
Record header:01 00 02 00 1C(其中1c表示下一条记录的偏移量)
行记录:69 6E 66 69 6D 75 6D 00 可以计算出来的第一条用户记录的偏移量就是c063 1c=0c7f
supremum:02 00 0B 00 00 73 75 70 72 65 6D 75 6D
Record header:02 00 0B 00 00
行记录:73 75 70 72 65 6D 75 6D
用户记录:
为了表示得更加清楚,我把两条虚拟记录位置也标注出来
c063->99
infimum:01 00 02 00 1C |69 6E 66 69 6D 75 6D 00
c070->112
supremum:02 00 0B 00 00 73 75 70 72 65 6D 75 6D
record1:
c07f>127
01 00 00 00 10 00 18 |80 00 00 01 |00 00 00 0C 81 E7 |B5 00 00 00 05 01 10 |61
record header:01 00 00 00 10 00 18
01表示变长字段,因为我们这里c1字段是变长字段,且占用空间小于255字节,占用1字节
00表示 bit map,因为我们这里c1字段不为空,因此为00
00 00 10 ->00000000 00000000 00010000,0000info flag,0000 record owned, 00000000 00010表示heap no=2
00 18表示下条记录的相对偏移量
80 00 00 01:主键ID,4字节,没有指明无符号,因此是80 xxx开头
00 00 00 0C 81 E7:trx_id,6字节
B5 00 00 00 05 01 10:回滚段指针,7字节
61:c1列,61表示字符a
record2:
c097->151
01 00 00 18 00 18 |80 00 00 02 |00 00 00 0C 81 E8 |B6 00 00 00 05 01 10 |
record header:01 00 00 18 00 18
00 00 18 -> 00000000 00000000 00011000,0000info flag,0000 record owned, 00000000 00011表示heap no=3
00 18表示下条记录的相对偏移量
分析同上
record3:
c08f->175
01 00 00 00 20 FF C1 |80 00 00 03 |00 00 00 0C 81 E9 |B7 00 00 00 05 01 10 |61
record header:01 00 00 00 20 FF C1
00 00 20 -> 00000000 00000000 00100000,0000info flag,0000 record owned, 00000000 00100表示heap no=4
分析同上
Page Directory:
00 70 00 63 page directory是倒序存储的,因此00 63是最初行的相对位置,即c063,也就是infimum记录的起始位置,00 70对应位置0c70,对应supremum的起始位置
page tailer
38 AD C1 F5 : checksum值
4E 07 B2 CA:对应LSN的低4字节
root@mysqldb 15:34: [xucl]> show create table t\G
*************************** . row ***************************
Table: t
Create Table: CREATE TABLE `t` (
`id` int(11) NOT AUTO_INCREMENT,
`c1` varchar(10) DEFAULT ,
`c2` varchar(10) DEFAULT ,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT= DEFAULT CHARSET=utf8mb4
row in set (0.00 sec)
root@mysqldb 15:34: [xucl]> select * from t;
---- ------ ------
| id | c1 | c2 |
---- ------ ------
| 1 | a | |
| 2 | a | a |
| 3 | | |
---- ------ ------
rows in set (0.00 sec)
提取用户记录部分
01 02 |00 00 10 00 1A |80 00 00 01 00 00 00 0C 81 ED BA 00 00 00 05 01 10 |61
01 01 00 |00 00 18 00 19 |80 00 00 02 00 00 00 0C 81 F5 C0 00 00 00 05 01 10 |61 61
03 |00 00 20 FF BE |80 00 00 03 00 00 00 0C 81 FD A6 00 00 00 05 01 10 |
为了观察和变长字段,我额外做了一个实验
01 02 -> 00000001 00000010,第一个字节表示变长字段c1长度为1,第二个字节表示第二个列c2为
01 01 00 -> 00000001 00000001 00000000,第一个字节表示变长字段c1长度为1,第二个字节表示变长字段c2长度为1,第三个字节表示没有字段为
03 -> 00000011,改字节表示第二个列、第三个列均为
上次遗留问题,关于heap_top如何计算出来?
heaptop的其实就是最后一条记录往后第一个free的位置,加入最后一条记录的偏移量为189,假设最后一行的长度为32字节(5字节record header 27字节),那么heaptop就是189 27=216了。
总结
简直手把手教了如何通过ue或者hexdump来观察innodb page结构
肉眼查看确实比较费力,建议还是使用专业工具
通过这种方法,能够对innodb page有更加深入的理解,例如上次遗留的问题就通过这次实验解惑了