一、索引页
在InnoDB中,数据是按页为单位进行存储的,一个页是16KB。如果概括的说,查询的逻辑就是 每次以页为单位读取数据到内存。然后再读取内存。
对于一个16K的数据页(官方叫索引页),其实并不都是存储的数据。如果存储的都是数据的话,不方便进行查询和分析。
所以呢,其实可以把数据页做一下拆分。使用一部分空间来存储汇总信息。
二、数据页结构
在InnoDB中,数据页分为以下模块。
结构名称 | 描述 |
---|---|
File Header | 文件头-用来存储页的一些通用信息 |
Page Header | 数据页专有的一些信息 |
Infimum、Supremum | 虚拟值 ,虚拟最大值,虚拟最小值 (这个值是数据库自己生成的一个标记) |
User Record | 用户数据(真正用来存储数据) |
Free Space | 空余空间 |
Page Directory | 页目录,在页目录中存储了一些数据的内存指针 |
File Trailer | 校验和 |
存储数据
数据存储在User Record中,具体的逻辑是 先从Free Space 中检查和申请空间,如果空间能够满足存储数据的要求,那么就划出这段空间用来存数据。那么,存储数据的空间,除了数据本身,还需要存储什么呢? 简单来说 每条存储的数据,除了数据本身,还有记录头信息。记录头信息主要有以下几个字段。
结构 | 说明 |
---|---|
预留位 | 没用到 |
预留位2 | 没用到 |
deleted_flag | 删除标记 |
min_rec_flag | B+树中每层非叶子节点的最小的目录项记录都会添加该标记 |
n_owned | 每组中最大的数据该字段记录为组内数据条数,不是最大的数据,该数据通通为0 |
head_no | 当前记录的位置指针 |
record_type | 用来标记记录的类型,0-普通数据;1-非叶子节点的目录项记录;2-Infimum记录;3-Supremum记录 |
next_record | 下一条记录的相对位置。这个记录的其实是当前数据到下一条记录的真实距离,正值表示从当前地址向后找;负值表示从当前地址向前找,所以,他不是插入的顺序,而是按照主键大小排列的数据顺序。 |
还记得之前背八股文的时候,说 MySQL的数据是通过链表连接的。next_record就是串联的链表。
head_no
head_no 因为每条数据的长度不一样,而且数据是紧密排列的。每新增一条记录,head_no都加1,而head_no 为0 和1 ,被系统默认占用了。head_no =0 则指的是 Infimum数据(虚拟的最小数据值),head_no =1 指的是Supremum。记录的顺序呢?记录是按照主键的顺序进行排序的。
数据查找
现在呢,已经知道了,数据是按照从小到大的顺序排列的,那么怎么高效的查询呢?其实最容易想到的,就是从最小到最大的遍历,但是这样还是太慢了。在MySQL中,用的是类似于二分的查找。
有没有可能跳跃着记录一些数据的大小呢?这样,每比较一个数据,都可以跳过一些值,速度自然也就快了。
槽槽槽槽
在MySQL中,会将正常的数据分组,因为数据都是按顺序排的,这时候,每组的最后一条数据都是最大的,上边的表格,提到过n_owned,最大的数据的n_owned记录了这一组有多少条数据。而将每组的最大的数据都提出来,集中放到一起(放到页尾),这就实现了上边说到的,跳跃着比较数据。这集中放在一起的各组老大的页面偏移量,叫做槽,存放槽的地方叫做页目录。那这样,就实现实现了一个跳跃的,近似的二分查找。
具体的查询逻辑如下:
1、通过二分,确定中间的槽,根据槽的值大小,继续二分,直到确定了组,然后遍历这个组,就可以查询到数据啦。