MySql索引入门

索引相关数据结构

Hash表

它的特点就是一个key通过hash计算可以得出一个位置,而通过这个位置可以直接得出对应的值。所以,它查找的时间复杂度是O(1)。无可避免的,这里不同的key可能会有相同的值,就会产生hash冲突,这个时候就需要一定的方法来解决冲突,常见的有拉链法,红黑树等。但是不变的是,只要冲突越多,查找就越慢。
如果使用Hash表索引,它相应的字段就不应该存在大量重复的值,这样的索引才有一定的效果。另一方面,由于hash是没有顺序的,如果业务中查询存在高频的范围查询语句,那么也不应该使用hash索引。


因此,hash表这种数据结构只适合只有等值查询的场景。常见的有nosql数据库,如Redis。

有序数组

顾名思义,使用有序数组实现的索引,可以使用二分查找进行查找,所以查找速度为log_2(n)。同样,因为是有序数组,所以范围查找的速度也非常优秀。
但是,在索引维护的过程中,难免会有插入或者更新。而如果新的数据不是在数组的头尾插入,就需要从数组内部扩展空间,这就涉及到数据大量的搬迁,此时索引的维护成本会直线上升。


因此,有序数组实现的索引更适合不再变动的数据,如一些历史信息记录表,只用于查询。那么就可以使用此类索引。

常见的树结构为二叉树, 一个父节点可以维护多个子节点,那么只有两个节点就成为二叉树。而左子节点比父节点小,右子节点比父节点大。按照这个规则,如果一个是一个相对于比较平衡的二叉树,那么它的查找效率也是趋向于log_2(n),同样范围查找也可以做到比较高效。但二叉树的层级受数据量的影响比较大。层级与数据量的关系为:log_k(N)+1,K为树的分叉,N为节点总数。所以如果数据库索引采用二叉树实现,维护树的平衡是一个难题,同时由于层高的原因,数据查找也需要经过大量的磁盘io。(因为父子节点可能不是在磁盘连续存储的)
那么有什么可以减少树的层高,使更多的数据连续存储在磁盘呢?那就是增加树的分叉,假设将分叉增加到1000,那么当我们有100w条数据时,也只有3层的高度,这样最多也是进行3次io。何况根节点是可以常驻于内存,可以再减少一次。
而利用这种多叉树结构实现的索引在MySql的Innodb引擎中被称为B+树,引擎中实现的分叉约为1400。

InnoDB 的索引模型

InnoDB的索引实现数据结构就是多叉树,这里称为B+树。

聚簇索引

InnoDB的每张表存储结构都是一棵树,而这棵存储树是基于什么值维护的呢?那就是表的主键,(无论如何表都会有一个唯一值)。除了叶子节点,每个节点都只存储了数据的主键。所有叶子节点则为表中所有的数据总和。
而这样,基于主键维护,存放了所有数据的索引被称为聚簇索引。也可以称为主键索引。

二级索引

二级所以即为不基于主键进行维护的索引。索引维护值即为对应创建索引的字段,而叶子节点存储的即为这行数据的主键。

因为二级索引存储的是主键,那么主键的大小就会影响整个二级索引的大小。为此,确定主键的类型也很重要。一般来说,主键应该尽量使用自增主键。它使用整型存储,空间占用小,性能也更高。

回表

假设表结构为下表,id为主键,age为二级索引。

id name age
1 hill 19
当执行select name from user where age = 19时,通过二级索引找到对应id,再从聚簇索引中通过id找到对应的数据,取出name返回。这个通过二级索引取id,在通过id找数据的过程就称为回表。

B+树索引的维护

要理解B+树索引如何维护,就得先了解叶子节点如何存储的。叶子节点的数据一般以数据页为最小管理单位,故数据读取时是以一个页的数据量进行读取。
数据页是有固定大小的,数据是有序的存放在数据页中。索引的维护就是涉及增删改,其实就是对数据页进行维护。
例如:插入一个数据,最后找到一个对应的数据页,如果该数据页还有剩余空间,维持数据的有序性,同时插入对应的位置即可。如果没有空间怎么办?那只能新增一个页用于数据的扩展,此时对磁盘的占用增多了,空间利用率就会下降。

而删除一个数据的时候就比较简单。找到对应的数据页,对数据进行删除即可。而删除多了,很容易留下空间碎片,这个时候应该对碎片较大的数据页进行合并,以提高空间利用率。

索引覆盖

索引覆盖指的是通过二级索引查询,不用经过回表这一过程,即可完成数据查询。那如何达到这种效果?以下表为例,有二级索引age,当执行select id from user where age = 19 时,通过age索引找到对应叶子节点,索引的值就是id。同时,select语句需要返回的字段只有id,那么就可以不通过回表,直接返回id。避免了回表这一步。这就称为索引覆盖。

id name age
1 hill 19

应用

  • 索引覆盖可以使用索引的key,也可以使用索引的value。如上例就是使用索引的value(主键id),另一种情况就是联合索引的key。
  • 创建联合索引时,不仅要考虑查询条件,还可以考虑返回值,添加冗余的联合索引字段可以利用索引覆盖减少回表。

最左前缀原理

最左前缀概念应用在联合索引中。首先,应该了解联合索引的组成方式。如:有一个联合索引是(name,age)。那么这个联合索引的每一条记录的顺序可以用下图表示。

索引key 张三,30 李四,10 王五,15 王五,30 王五,44
索引value(id) 1 10 20 21 22
可以简单总结出,联合索引是根据定义时的字段顺序进行优先排序。而在左边的字段排序完成,再到下一位进行排序。
所以查询语句select age from user where name = 王五可以使用到联合索引。而查询语句select name from user where age >10是无法使用这个联合索引的。
了解联合索引的组成,最左匹配原则就可以简单的总结为:联合索引的建立根据字段定义的顺序,查询语句索引字段需从左到右匹配,并且不能断层。如:有联合索引(name,age,sex),若执行select name age sex from user where name='张三' and sex='男' ,则只能使用联合索引的第一个字段name。

简单应用

最左前缀原理理解很简单,难的是如何在实际上应用,下面介绍一下常见的应用。

  • 创建联合索引时,高频的查询的字段应该放在最左边。
  • 当存在a,b两个字段单独查询、联合查询3种情况时。建立一个联合索引和一个单独索引即可。原则是:字段占用空间较大的放在联合索引最左。

索引下推

索引下推可以理解为索引覆盖的退化版。如下例所示:有联合索引(name,age,sex)

索引key 张三,30,男 李四,10,男 王五,15,男 王五,30,女 王五,44,男
索引value(id) 1 10 20 21 22
当执行语句select * from user where name='王五' and sex='女' 时,可以得出目标'王五'有3条记录,这个时候因为无法再利用索引覆盖,是必须要进行回表的。但同时,联合索引中还包含了sex这个字段,而查询的过滤条件又包含的sex这个字段。所以,在回表之前,可以先利用这个联合索引进行初步过滤,把sex!='女'的过滤掉,这样就可以减少回表的次数。上面回表次数就从3次减少为一次了。
posted @ 2021-09-13 15:27  半生瓜丶  阅读(53)  评论(0编辑  收藏  举报