根据前面内容知道了各个数据页可以组成一个双向链表
,而每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表
,每个数据页都会为存储在它里边儿的记录生成一个页目录
,在通过主键查找某条记录的时候可以在页目录
中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录
根据页号查找怎么定位我们知道了。但是怎么定位数据在哪个页呢
?由于我们并不能快速的定位到记录所在的页,所以只能从第一个页沿着双向链表一直往下找
仿照普通数据记录的存储方式,我们将每个页也做个目录。但是我们并不需要页的所有信息,只需要一个页号
就能定位一个页。怎么筛选我们需要找的是哪个页面呢?用户记录
的每条数据都有唯一的主键
并且依次递增形成了一个单链表
。所以我们只需要记录每个页的最小主键
,并且按最小主键
依次递增排序。
比方说我们想找主键值为20
的记录,具体查找过程分两步:
- 先从目录项中根据二分法快速确定出主键值为
20
的记录在目录项3
中(因为12 < 20 < 209
),它对应的页是页9
。 - 再根据前面说的在页中查找记录的方式去
页9
中定位具体的记录。
一、目录项记录
复用了之前存储用户记录的数据页来存储目录项,为了和用户记录做一下区分,我们把这些用来表示目录项的记录称为目录项记录
InnoDB
区分一条记录是普通的用户记录
还是目录项记录
是用了行格式
的record_type
属性
0
:普通的用户记录1
:目录项记录2
:最小记录 (Infimum)3
:最大记录 (Supremum)
虽然说目录项记录
中只存储主键值和对应的页号,比用户记录需要的存储空间小多了,但是不论怎么说一个页只有16KB
大小,能存放的目录项记录
也是有限的,那如果表中的数据太多,以至于一个数据页不足以存放所有的目录项记录
就重新分配一个目录项记录
页。
如果我们表中的数据非常多则会产生很多存储目录项记录
的页,那我们怎么根据主键值快速定位一个存储目录项记录
的页呢?其实也简单,为这些存储目录项记录
的页再生成一个更高级的目录,就像是一个多级目录一样,大目录里嵌套小目录,小目录里才是实际的数据,所以现在各个页的示意图就是这样子:
如果简化一下,那么我们可以用下面这个图来描述它:
这是一种数据结构,它的名称是
B+
树。
B+
树最上面的那个节点也称为根节点
。- 用来存放
目录项
的节点称为非叶子节点
或者内节点
- 我们的实际用户记录其实都存放在B+树的最底层的节点上,这些节点也被称为
叶子节点
或叶节点
假设所有存放用户记录的叶子节点代表的数据页可以存放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层,
二、聚簇索引
我们上面介绍的B+
树本身就是一个目录,或者说本身就是一个索引。它有两个特点:
-
使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:
- 页内的记录是按照主键的大小顺序排成一个
单向链表
。 - 各个存放用户记录的页也是根据页中用户记录的
主键
大小顺序排成一个双向链表
。 - 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中
目录项记录
的主键
大小顺序排成一个双向链表
。
- 页内的记录是按照主键的大小顺序排成一个
-
B+
树的叶子节点存储的是完整的用户记录。
所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。
我们把具有这两种特性的B+
树称为聚簇索引
,所有完整的用户记录都存放在这个聚簇索引
的叶子节点处。这种聚簇索引
并不需要我们在MySQL
语句中显式的使用INDEX
语句去创建(后边会介绍索引相关的语句),InnoDB
存储引擎会自动的为我们创建聚簇索引。另外有趣的一点是,在InnoDB
存储引擎中,聚簇索引
就是数据的存储方式(所有的用户记录都存储在了叶子节点
),也就是所谓的索引即数据,数据即索引。
三、二级索引
上面介绍的聚簇索引
只能在搜索条件是主键值时才能发挥作用,因为B+
树中的数据都是按照主键进行排序的。那如果我们想以别的列作为搜索条件该咋办呢?难道只能从头到尾沿着链表依次遍历记录么?
不,我们可以多建几棵B+
树,不同的B+
树中的数据采用不同的排序规则。比方说我们用除主键
外的其他单个列
作为索引数据,再建一棵B+
树,这种B+
树也被称为二级索引
,效果如下图所示:
这个B+
树与上面介绍的聚簇索引有几处不同:
-
使用
索引列
的大小进行记录和页的排序,这包括三个方面的含义:- 每个页是按照
索引列
的大小顺序排成一个单向链表。 - 页内的用户记录是根据
索引列
大小(如果相同根据主键排序)顺序排成一个双向链表。 - 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的
索引列
大小顺序排成一个双向链表。
- 每个页是按照
-
B+
树的叶子节点存储的并不是完整的用户记录,而只是索引列+主键
这两个列的值。 -
目录项记录(非叶子节点)中不再是
主键+页号
的搭配,而变成了索引列+页号+主键
的搭配。因为索引列
的值不唯一,所以需要存储主键去定位
如果我们想查索引列
和主键列以外的其他值就必须再根据主键值去聚簇索引中再查找一遍完整的用户记录。
这个过程也被称为回表
四、联合索引
我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列
建立索引称为联合索引
,比方说我们想让B+
树按照c1
和c2
列的大小进行排序,这个包含两层含义:
- 先把各个记录和页按照
c1
列进行排序。 - 在记录的
c1
列相同的情况下,采用c2
列进行排序,c2
列相同的情况下按照主键排序
注意:
排序顺序和建立顺序有关(最左匹配原则关键点
)
- 如果我们按c1,c2,c3建立,排序就会先按c1排序,c1相同按c2,c2相同按c3排序。
- 如果我们按c3,c2,c1建立,排序就会先按c3排序,c3相同按c2,c2相同按c1排序