MySQL
服务器上负责对表中数据的读取和写入工作的部分是存储引擎
,InnoDB
是MySQL
默认的存储引擎,也是我们最常用到的存储引擎。InnoDB
是一个将表中的数据存储到磁盘上的存储引擎,所以即使关机后重启我们的数据还是存在的。
而真正处理数据的过程是发生在内存中的,所以需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。而我们知道读写磁盘的速度非常慢,和内存读写差了几个数量级,所以当我们想从表中获取某些记录时,InnoDB
存储引擎需要一条一条的把记录从磁盘上读出来么?不,那样会慢死,InnoDB
采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
我们存储的记录在页中按照主键值由小到大顺序串联成一个单链表,页中把记录制作了一个目录,制作过程:
- 将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组。
- 每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的
n_owned
属性表示该记录拥有多少条记录,也就是该组内共有几条记录。 - 将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近
页
的尾部的地方,这个地方就是所谓的Page Directory
,也就是页目录
。页面目录中的这些地址偏移量被称为槽
(英文名:Slot
),所以这个页面目录就是由槽
组成的。
InnoDB
存储引擎对每个分组中的记录条数是有规定的:对于最小记录所在的分组只能有 1 条记录,最大记录所在的分组拥有的记录条数只能在 1~8 条之间,剩下的分组中记录的条数范围只能在是 4~8 条之间。所以分组是按照下边的步骤进行的:
- 初始情况下一个数据页里只有最小记录和最大记录两条记录,它们分属于两个分组。
- 之后每插入一条记录,都会从
页目录
中找到主键值比本记录的主键值大并且差值最小的槽,然后把该槽对应的记录的n_owned
值加1,表示本组内又添加了一条记录,直到该组中的记录数等于8个。 - 在一个组中的记录数等于8个后再插入一条记录时,会将组中的记录拆分成两个组,一个组中4条记录,另一个5条记录。这个过程会在
页目录
中新增一个槽
来记录这个新增分组中最大的那条记录的偏移量。
在一个数据页中查找指定主键值的记录的过程分为两步:
- 通过二分法确定该记录所在的槽,并找到该槽所在分组中主键值最小的那条记录。
- 通过记录的
next_record
属性遍历该槽所在的组中的各个记录。
我们现在知道了各个数据页可以组成一个双向链表
,而每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表
,每个数据页都会为存储在它里边儿的记录生成一个页目录
,在通过主键查找某条记录的时候可以在页目录
中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。
而我们如果想从许多页中根据主键值快速定位某些记录所在的页,我们需要给这些页面做个目录,每个页对应一个目录项,且下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。每个目录项包括下边两个部分:
- 页的用户记录中最小的主键值,我们用
key
来表示。 - 页号,我们用
page_no
表示。
如果我们表中的数据非常多则会产生很多存储目录项记录
的页,那我们怎么根据主键值快速定位一个存储目录项记录
的页呢?其实也简单,为这些存储目录项记录
的页再生成一个更高级的目录,就像是一个多级目录一样,大目录里嵌套小目录,小目录里才是实际的数据,所以现在各个页的示意图就是这样子:
其实这是一种组织数据的形式,或者说是一种数据结构,它的名称是B+
树。
不论是存放用户记录的数据页,还是存放目录项记录的数据页,我们都把它们存放到B+
树这个数据结构中了,所以我们也称这些数据页为节点
。从图中可以看出来,我们的实际用户记录其实都存放在B+树的最底层的节点上,这些节点也被称为叶子节点
或叶节点
,其余用来存放目录项
的节点称为非叶子节点
或者内节点
,其中B+
树最上边的那个节点也称为根节点
。
从图中可以看出来,一个B+
树的节点其实可以分成好多层,最下边的那层,也就是存放我们用户记录的那层为第0
层,之后依次往上加。假设所有存放用户记录的叶子节点代表的数据页可以存放100条用户记录,所有存放目录项记录的内节点代表的数据页可以存放1000条目录项记录,那么:
- 如果
B+
树只有1层,也就是只有1个用于存放用户记录的节点,最多能存放100
条记录。 - 如果
B+
树有2层,最多能存放1000×100=100000
条记录。 - 如果
B+
树有3层,最多能存放1000×1000×100=100000000
条记录。 - 如果
B+
树有4层,最多能存放1000×1000×1000×100=100000000000
条记录。哇咔咔~这么多的记录!!!
你的表里能存放100000000000
条记录么?所以一般情况下,我们用到的B+
树都不会超过4层,那我们通过主键值去查找某条记录最多只需要做4个页面内的查找(查找3个目录项页和一个用户记录页),又因为在每个页面内有所谓的Page Directory
(页目录),所以在页面内也可以通过二分法实现快速定位记录!