索引的出现其实就是为了提高数据查询的效率。

索引常见的模型

哈希表

适合等值查询,不适合范围查询。因为会出现哈希冲突,冲突后采用链地址法解决。就会导致发生冲突后要遍历整个链表。

有序数组

适合等值查询以及范围查询。有序数组在单值查询可以使用二分法快速查找,范围找到一个然后往左右遍历即可,但是它插入数据时要维护这个有序数组就比较麻烦,适合用于静态存储引擎。

搜索树

二叉搜索树的特点是:左边小,右边大(相对根节点)查找效率和二分一样,O(LogN)。但是为了保证这个效率,就需要保证它是平衡二叉树(考虑插入数据是正序或者倒序情况,会退化成链表),为了确保平衡,插入时间复杂度也是O(LogN)

多叉树:孩子节点从左到右依次递增。

二叉树搜索效率最高,但是往往采用多叉树,因为索引还要写入磁盘,意味着我们查询索引可能也要读取磁盘。考虑一种情况,100万数据量,那么二叉树高20,如果这些索引没有在内存当中,那么就需要去读取20个数据块。

每次从磁盘读取数据都是按照数据块来读取的,而不是仅仅查找那一条记录。而MySQL并不是一开始就把所有的索引都加载到内存当中,而是按需加载。MySQL有一个缓冲池,当需要访问索引时,会先在缓冲池中查看有没有,如果没有就从磁盘读取,然后写入缓冲池中。

N叉树的N一般是1200。考虑到树根的数据块总是在内存中,一个10亿行的表上一个整数字段的索引,查找一个值最多只需要访问3次磁盘。

InnoDB索引模型

假如有如下表:

ID k name
100 1 null
200 2 null
300 3 null
500 5 null
600 6 null

其中ID是主键,k上有索引。那么它的索引结构大致如下:

image-20230331104745662

在InnoDB中,索引类型分为主键索引和非主键索引。

主键索引的叶子节点存的是整行数据。在InnoDB里,主键索引也被称为聚簇索引(clustered index)。

非主键索引的叶子节点内容是主键的值。在InnoDB里,非主键索引也被称为二级索引(secondary index)。

其中,如果是按照主键索引查询,那么只会搜索上图的第一颗树,然后找到对应记录直接返回行数据即可。但是如果是按照k这个索引来查询,那么会现根据k索引查找到对应数据的主键,然后去第一颗树中查找对应的实际数据。

索引维护

索引的插入可能会导致数据的其他数据的移位,在极端情况下,如果要插入的索引页满了,那么就会导致页分裂。这个页分裂的过程就是B+树页分裂的过程。

最重要的一点,B+树的索引是建立在数据所在的数据页上的,而不是直接建立在数据。其实通过索引找到数据页后,还要在数据页中去找具体的数据,才能返回。

回表

1
select * from T where k between 3 and 5

首先在非主键索引树上找到k = 3的值,取得ID = 300,然后去主键索引树上查找ID = 300所对应的具体值。然后在非主键索引树上找下一个值,k = 5,找ID,然后拿ID去查主键索引。然后继续去下一个,发现k = 6,大于5,查找结束。

这个从非主键索引查找完去主键索引查找的过程就叫做回表。

覆盖索引

1
select ID from T where k between 3 and 5

执行这个语句时,虽然会走非主键索引,但是索引的值存的是ID,即索引里面包含了所有要查询的数据,这就是覆盖索引。由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

最左前缀原则

比如我们建立了一个(姓名,年龄)的索引,那么它可以用来匹配按名字的查找,但是按照年龄就不行。这个最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符。

索引下推

比如我们已经建立了(姓名,年龄),此时要执行以下SQL:

1
select * from tuser where name like '张%' and age=10 and ismale=1;

假如数据库表数据如下:

ID name age ismale
1 李四 2 1
2 123 10 1
3 张三 10 1
4 张三 10 1
5 张三 30 1
6 张六 40 0

在执行这条语句时,如果没有索引下推,则每次按索引找到姓张的后,都需要回表去查看对应的一整行数据,来判断后两个字段是否一致。但是有了索引下推,可以在这里直接判断age是否满足条件,可以减少回表的次数。注意,这里的索引是联合索引,如果没有下推,即便是索引里有age字段,也不会进行判断。

参考

《MySQL45讲》