InnoDB数据(索引)页<二>目录项结构

1,242 阅读7分钟

根据前面内容知道了各个数据页可以组成一个双向链表,而每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表,每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录

image.png

根据页号查找怎么定位我们知道了。但是怎么定位数据在哪个页呢?由于我们并不能快速的定位到记录所在的页,所以只能从第一个页沿着双向链表一直往下找

仿照普通数据记录的存储方式,我们将每个页也做个目录。但是我们并不需要页的所有信息,只需要一个页号就能定位一个页。怎么筛选我们需要找的是哪个页面呢?用户记录的每条数据都有唯一的主键并且依次递增形成了一个单链表。所以我们只需要记录每个页的最小主键,并且按最小主键依次递增排序。

image.png

比方说我们想找主键值为20的记录,具体查找过程分两步:

  • 先从目录项中根据二分法快速确定出主键值为20的记录在目录项3中(因为 12 < 20 < 209),它对应的页是页9
  • 再根据前面说的在页中查找记录的方式去页9中定位具体的记录。

一、目录项记录

复用了之前存储用户记录的数据页来存储目录项,为了和用户记录做一下区分,我们把这些用来表示目录项的记录称为目录项记录

InnoDB区分一条记录是普通的用户记录还是目录项记录是用了行格式record_type属性

  • 0:普通的用户记录
  • 1:目录项记录
  • 2:最小记录 (Infimum)
  • 3:最大记录 (Supremum)

虽然说目录项记录中只存储主键值和对应的页号,比用户记录需要的存储空间小多了,但是不论怎么说一个页只有16KB大小,能存放的目录项记录也是有限的,那如果表中的数据太多,以至于一个数据页不足以存放所有的目录项记录就重新分配一个目录项记录页。

image.png

如果我们表中的数据非常多则会产生很多存储目录项记录的页,那我们怎么根据主键值快速定位一个存储目录项记录的页呢?其实也简单,为这些存储目录项记录的页再生成一个更高级的目录,就像是一个多级目录一样,大目录里嵌套小目录,小目录里才是实际的数据,所以现在各个页的示意图就是这样子:

image.png

如果简化一下,那么我们可以用下面这个图来描述它:

image.png 这是一种数据结构,它的名称是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+树本身就是一个目录,或者说本身就是一个索引。它有两个特点:

  1. 使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:

    • 页内的记录是按照主键的大小顺序排成一个单向链表
    • 各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表
    • 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录主键大小顺序排成一个双向链表
  2. B+树的叶子节点存储的是完整的用户记录。

所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。

我们把具有这两种特性的B+树称为聚簇索引,所有完整的用户记录都存放在这个聚簇索引的叶子节点处。这种聚簇索引并不需要我们在MySQL语句中显式的使用INDEX语句去创建(后边会介绍索引相关的语句),InnoDB存储引擎会自动的为我们创建聚簇索引。另外有趣的一点是,在InnoDB存储引擎中,聚簇索引就是数据的存储方式(所有的用户记录都存储在了叶子节点),也就是所谓的索引即数据,数据即索引。

三、二级索引

上面介绍的聚簇索引只能在搜索条件是主键值时才能发挥作用,因为B+树中的数据都是按照主键进行排序的。那如果我们想以别的列作为搜索条件该咋办呢?难道只能从头到尾沿着链表依次遍历记录么?

  不,我们可以多建几棵B+树,不同的B+树中的数据采用不同的排序规则。比方说我们用除主键外的其他单个列作为索引数据,再建一棵B+树,这种B+树也被称为二级索引,效果如下图所示:

image.png

这个B+树与上面介绍的聚簇索引有几处不同:

  • 使用索引列的大小进行记录和页的排序,这包括三个方面的含义:

    • 每个页是按照索引列的大小顺序排成一个单向链表。
    • 页内的用户记录是根据索引列大小(如果相同根据主键排序)顺序排成一个双向链表。
    • 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的索引列大小顺序排成一个双向链表。
  • B+树的叶子节点存储的并不是完整的用户记录,而只是索引列+主键这两个列的值。

  • 目录项记录(非叶子节点)中不再是主键+页号的搭配,而变成了索引列+页号+主键的搭配。因为索引列的值不唯一,所以需要存储主键去定位

如果我们想查索引列和主键列以外的其他值就必须再根据主键值去聚簇索引中再查找一遍完整的用户记录。这个过程也被称为回表

四、联合索引

我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引称为联合索引,比方说我们想让B+树按照c1c2列的大小进行排序,这个包含两层含义:

  • 先把各个记录和页按照c1列进行排序。
  • 在记录的c1列相同的情况下,采用c2列进行排序,c2列相同的情况下按照主键排序

image.png

注意:

排序顺序和建立顺序有关(最左匹配原则关键点

  • 如果我们按c1,c2,c3建立,排序就会先按c1排序,c1相同按c2,c2相同按c3排序。
  • 如果我们按c3,c2,c1建立,排序就会先按c3排序,c3相同按c2,c2相同按c1排序