在讲页结构之前,先说一下InnoDB的整体结构:
(主要是为了让小伙伴知道说的是在哪一层。。)
简单说就是 表空间是InnoDB存储引擎逻辑结构的最高层,表里面有段,段里面有区,区里面有页,页里面有行,行有具体的行格式。当然页也有自己的结构,这里主要讲的就是页结构。
在InnoDB中,管理存储空间的基本单位是页,一个页大小一般是16KB。平时我们将记录insert,InnoDB其实是将这个记录存放在页中的,InnoDB中,常见的页类型有:
- 数据页(B-tree Node)
- undo页 (undo Log Page)
- 系统页(System Page)
- 事务数据页(Transaction system Page)
- 插入缓冲位图页(Insert Buffer Bitmap)
- 插入缓冲空闲列表页(Insert Buffer Free List)
- 未压缩的二进制大对象页(Uncompressed BLOB Page)
- 压缩的二进制大对象页(compressed BLOB Page)
这里主要讲的是InnoDB的数据页的结构:
File header | 文件头部 | 38字节 | 页的一些通用信息 |
---|---|---|---|
Page Header | 页面头部 | 56字节 | 数据页专用的一些信息 |
infimun + Supremum Records | 最小记录和最大记录 | 26字节 | 两个虚拟的行记录 |
User Records | 用户记录 | 不确定 | 实际存储的行记录内容 |
Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
Page Directory | 页面目录 | 不确定 | 页中的某些记录的相对位置 |
File Trailer | 文件尾部 | 8字节 | 检验页是否完整 |
一页的大小一般是16KB换成字节也就是 16384个字节,也就是说,UserRecords+FreeSpace+PageDirectory = 16384-38-56-26-8=16256字节
- Infimun+Supremum
从mysql的ibd文件中查看这两个属性,不知道自己的mysql的ibd文件在哪的小伙伴可以mysql中执行一下这行命令
show VARIABLES like 'datadir';
然后去对应的路径里面找就是了,每个表都会有一个ibd文件。
下面我们在这个文件里面找一下这两个属性。
这两个属性都是都是由5个字节的头信息 和 8个字节表示单词的信息构成
这里列出来一下这两个属性的最后8个字节:
infimum
最后一个00补位作用
supremum
在讲解前面5个字节的头信息之前,必须头信息的结构,这里的头结构和另一篇博客的记录头信息结构是一样的,这里先贴出来头信息的结构:
名称 | 大小(单位:bit) | 描述 |
---|---|---|
预留位1 | 1 | 没有使用 |
预留位2 | 1 | 没有使用 |
delete_mask | 1 | 标记该记录是否被删除 |
min_rec_mask | 1 | B+树的每层非叶子节点中的最小记录都会添加该表记 |
n_owned | 4 | 表示当前记录拥有的记录数 |
heap_no | 13 | 表示当前记录在记录堆的位置信息 |
record_type | 3 | 表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录 |
next_record | 16 | 表示下一条记录的相对位置 |
一共占用40big,也就是5个字节
这里再列出来一下这两个属性的前5个字节的信息:
Infimum
逐个讲解一下 00 02 01 1d 69 注意啊,这里的是16进制,1个16进制需要4个2进制位表示,这里一共10个16进制,那么换成2进制需要40个2进制位表示,和之前说的5个字节,5*8=40个2进制位也是能对应上的。
00换成2进制
0000 0000
02换成2进制
0000 0010
01换成2进制
0000 0001
1d换成2进制
0001 1101
69换成2进制
0110 1001
连在一起,也就是
0000 0000 0000 0010 0000 0001 0001 1101 0110 1001
对照着上面的头信息表结构简单解读一下:
0 | 0 | 0 | 0 | 0000 | 0000 0010 0000 0 | 001 | 0001 1101 0110 1001 |
---|---|---|---|---|---|---|---|
预留1 | 预留2 | delete_mask | min_rec_mask | n_owned | heap_no | record_type | next_record |
- delete_mask:表示这条记录是否被删除,1删除 0没被删除。
- min_rec_mask:B+树的每层非叶子节点中的最小记录都会添加该表记。
- n_owned:表示当前记录拥有的记录数。
- UserRecords + FreeSpace。
- heap_no:表示当前记录在记录堆的位置信息 。
- record_type:表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录。
- next_record:表示下一条记录的相对位置。
这里看这里的这个信息:
delete_mask是0,表示这里的这个infimum属性的伪头部信息这个没有被删除
min_rec_mask是0,表示这不是非叶子节点中的最小记录
n_owned是0,表示在infimum分组里面记录数为条。
heap_no是64,表示Infimum伪记录在页内的行记录相对位置为64.
record_type是1,表示这条伪记录是非叶子节点的记录,注意,这是是非叶子节点,但不是最小的记录。和上面的n_owned是0对应上,因为这条伪记录,是叶子节点,但不是最小的,所以和记录数n_owned等于0条记录对应上。
next_record是7529,表示当前记录(伪记录)到下一条真实记录的位置是7529个字节(根据这个来找表内的第一条数据),这里解释一下,下一条相距7529个字节,直接往后翻,就可以看到第一条数据。
这是我插入的数据。
这里可以在ibd文件中看到aaaa这个第一条数据
Supremum
字节信息:
03 000b 0000
0 | 0 | 0 | 0 | 0011 | 0000 0000 0000 1 | 011 | 0000 0000 0000 0000 |
---|---|---|---|---|---|---|---|
预留1 | 预留2 | delete_mask | min_rec_mask | n_owned | heap_no | record_type | next_record |
这里看这里的这个信息:
delete_mask是0,表示这里的这个Supremum属性的伪头部信息这个没有被删除
min_rec_mask是0,表示这不是非叶子节点中的最小记录
n_owned是3,表示在Supremum分组里面记录数为3条。
heap_no是1,表示Supremum伪记录在页内的行记录相对位置为1.
record_type是3,表示这个记录是最大的一条,和Supremum记录类型 的值一致,因为它本就是记录最大的一条数据的,并且这里的n_owned为3,里面存在3条。
next_record是0,表示当前记录的真实数据到下一条真实记录的位置是0.也就是没有下一条数据了。
-
UserRecords + FreeSpace
当插入一个记录时,会根据我们指定的行格式,存储到User Records中,但是在一个页刚刚生成的时候,也就是里面一条数据都没有插入过的时候,UserRecords其实是不存在这个部分的。
每次插入一个数据,就会从FreeSpace中分出一点空间给UserRecords,用作存储记录。当FreeSpace的空间被划分完了,也就是FreeSpace这个部分不存在了,也就表明这个页的空间用完了,如果还要插入,就要去获取新的页。 -
Page Directory
记录在页中是按照主键的大小依次串联成一个单链表的,如果有主键,则按照主键的大小串联成单链表,没有主键有unique的字段,如果unique也没有,InnoDB会自己给你加一个隐藏的列,Row_id。如果我们要查找一个记录,遍历这个单链表可以找到结果,不过这个方法如果这个链表很长的话,就会很耗时,在InnoDB肯定不会这样去找,它是这样解决的:
- 将所有正常的记录(包括最小最大记录,但是不包括标记为已删除的记录)划分为几个组。
- 每个组的最后一条记录(也就是这个组里面最大的这条记录)的头信息的n_owned属性的值表示这个组里面拥有多少条记录。
- 将每个组的最后一条记录,也就是这个组的最大的记录单独提取出来,存储到靠近页的尾部的地方,这个地方就是Page Directory,和我们书本的最下面的页码一样。只不过这本书下面记录的不只是一个页码。这个东西称之为页目录。页目录里面存储的“页码”称为槽,实际生活中的书本的目录由很多页码组成,对应的,这里的页目录由很多槽组成。
画图实在画不动了,这里借用一下别人的图,侵删,感谢!
这里就是槽的概念和PageDirect的直观图。
看到这里,有没有发现这和B+树很像呢,这样设计也是为了提高查找效率,
在一个页中,假设有100条数据.
这样就更像B+树了,要是还是看不出来,可以试着,横过来看看,看完回来,是不是发现了一颗B+树。
- File Trailer
这个部分主要是为了校验页的完成性。一共占了8个字节
前4个字节:代表页的校验和。
后4个字节:代表最后修改时对应的日志序列位置(LSN-Log Sequence Number)
注意:后4个字节,只是用来记录LSN的位置,而不是具体的数值,可以通过
可以在mysql里面运行:show engine innodb status\G;里面可以看到LSN的具体值。
这里说最后一点东西,这是执行上面的命令得到的信息,贴在下面的是有关缓冲池的信息,和一点解释,后面一篇将会介绍缓冲池和行结构。
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137363456
Dictionary memory allocated 391661
Buffer pool size 8192 #表示缓冲池一共 8192页,一页16k,即 8192 * 16kb 即之前查的 128M的缓冲池
Free buffers 7207 #表示当前Free列表中的页的数量
Database pages 980 #表示LRU列表中页的数量(Laster Recent Used) 7207 + 980 = 8187 != 8192 因为缓冲池中的页还要分配给自适应哈希索引、Lock信息、Insert Buffer等页,而这些页不需要使用LRU算法进行维护,因此不存在LRU列表中。
Old database pages 381
Modified db pages 0 #脏页的数量
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 838, created 142, written 164
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 980, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]