# 简介

我们使用 sql 语句查询某条记录时

select * from 表名 where 列名 = xxx;

where 后面跟着的是查询条件,我们之前学的,页中的记录是根据主键排序的(如果没有主键,就根据不能为 NULL 的唯一的数据排序,如果这样的属性也没有,就按照 row_id 排序)。

那么在没有创建索引的情况下,如果我们的查询条件是根据主键查询,那么在页中就可以通过二分查找快速获取数据,但是如果 where 后面跟着的是一个非主键属性,那么在页中就只能从 InfimumSupremum 一条一条的顺序查找一下子就慢很多

所以我们必须找到一种高效的搜索方法 —— 索引。

# 索引

先建一个表,后面方便演示例子。

create table index_demo (
    c1 int,
    c2 int,
    c3 char(1),
    primary key (c1)
) row_format = COMPACT;

我们知道,一个页默认只有 16KB,存储的记录是有限的,所以一个表往往需要很多个页,页之间的记录也必须保持顺序,也就是说,页 A 的记录主键值必须大于上一个页的记录主键值,小于下一页的记录的主键值(当然,这里面不包含 InfimumSupremum )。

前文讲页结构时, File Header 中对于页号的规定使得数据页之间连成双向链表,我们期望为快速定位记录所在的数据页而建立一个别的目录,该目录实现:

  • 下一个数据页的记录的主键 > 上一个数据页记录的主键。

为了实现这个状态,存在页分裂的机制。假设上一个页已经满了,最后一条记录主键是 100,现在又加入一条记录,主键为 90。那么新加入的这条记录就会放到该页末尾,主键为 100 的记录会被分到新的一个页。

  • 给所有页建立一个目录项

此处是关于页的目录其实对于页的查找也是二分(就是根据页目录,之前的槽 Slot 是关于记录的 Page Directory )。每个页对应一个目录项,每个目录项包括两部分:页的最小主键值 + 页号 page_no 。插入记录时,根据主键值,在所有目录项下进行二分查找,找到合适的页后,根据页中的 Page Directory 进行二分查找,然后页分裂(如果有的话)。最重要的就是,这个目录就叫 —— 索引

# 简单的索引方案

为了实现通过目录项来进行二分查找,我们也要管理目录项,使其在增删改查时保持有序,所以也把它们放在页中进行管理,这种记录就是目录项记录 record_type = 1 (你还记得 record_type 为 0,2,3 时是什么意思吗), 该记录也就只有主键值和页号,当然头信息也是存在的,因为只有主键值(如果不是变长字段或变长字符集),所以没有变长字段长度列表,也没有 NULL 列表。

不断累积,目录项页也存在目录项,这样的目录项又被放进新的目录项页(只不过在上一层),不断向上递归,形成 B + 树。

说了那么多,不看图总是懵逼的。

image-20220728104845461

这个图画的我难受。。。。

上图为了简略,一个记录的内容我只画了: record_type + next_record + c1 + c2 + c3

从图中可以看出,我们真正的记录是放在 B + 树中的叶子节点,存放目录项记录的都是内节点。

Page Header 里面有个属性就是 PAGE_LEVEL 表示该页在 B + 树中的层级。其实我们谈索引,什么对某个属性 A 创建索引,其实就是创建以 A 排序的 B + 树(当然,这么说不准确,B + 树内容不同,但是你可以这么理解)。我们一开始创建表,指定 c1 为主键,那么我们插入记录时, InnoDB 就会维护上图的 B + 树,使其有序。如果我们创建关于 c2 的索引,我们就会得到一个关于 c2 排序的 B + 树。

索引的 B + 树和真正的主键的 B + 树有什么不同,之后讲到回表你就懂了

# 索引种类

聚簇索引:在 InnoDB 中,聚簇索引就是数据的存储方式,所有记录都存在叶子节点,“索引即数据,数据即索引”

二级索引:聚簇索引只有在搜索条件为主键时才能发挥作用,如果以其他列为搜索条件,就要创建关于他们的索引(B + 树)才行,这就是二级索引。假设对 c2 创建索引,该二级索引 B + 树的叶子节点的记录存储的就是 c2 + 主键值。当我们在二级索引中找到对应的 c2 ,我们就可以拿到对应的主键值,然后再去聚簇索引中找到真正完整的数据,这就是回表。二级索引之所以不存储完整的记录,那肯定是为了节省空间啊

联合索引:其本质就是二级索引,因为有时我们会这么查询

select * from index_demo where c2 = 1 and c3 = '1';

所以我们此时就要创建关于 c2,c3 的联合索引。

一个索引的根页面,一旦被创建,其页号就不会改变。一开始是没有数据的,每当存在数据插入,都是在这个根节点下插入

# sql 语句

# key和index是同义词,随便指定一个即可
create table 表名 (
    各个列信息
    (key|index) 索引名 (要指定的列)
)

alter table 表名 add (key|index) 索引名 (要指定的列)

# 删除索引
alter table drop (index|key) 索引名