1.前言
1.1 什么是索引
索引是为了提高查询效率而引入的一种数据结构,类似于用字典查词典,在偌大词量的词典里想找到一个词或几个词,一条一条的去找效率的非常低的,但是如果有一个基于大家都熟识的属性(例如拼音)建立的有序的词典"目录",从目录可以得到这个词的大致位置,再从这个位置出发去检索数据,能很大程度上提高检索的效率。索引可以简单理解为数据库的目录,为了提高查询效率而建立的数据结构。
1.2 索引的类型
1.2.1 普通索引
最常见的索引,用来检索数据。
1.2.2 唯一索引
唯一索引用来作为数据一致性约束条件。
1.2.3 全文索引
提供类似搜索引擎的功能。
1.3 索引的实现方式
1.3.1 hash索引
使用表的一个或多个字段的hash值作为索引的key,能够快速定位到一条数据。优势:O(1)的查询时间复杂度,需要的存储空间少;劣势:无法提供区间查询,hash值会发生冲突,无法提供排序功能。
1.3.2 B+Tree索引
相比hash索引提供的一次命中,树形索引可能需要更多的存储空间,但树形数据结构对区间查询以及排序能够提供支持,几种可能的树形数据结构如下。
1.二叉排序树
二叉排序树的查询平均时间复杂度为O(LogN),但在极端情况下会退化成链表,访问复杂度为O(N)。
2.平衡二叉树
平衡二叉树是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
平衡二叉树解决了二叉排序树极端情况下退化成链表的情况,但平衡二叉树维持平衡需要大量的旋转,维持平衡的代价较大。
3.红黑树
红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:
- 每个节点要么是黑色,要么是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色。
- 每个红色结点的两个子结点一定都是黑色。
- 任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
红黑树是一颗类平衡二叉树,不满足二叉平衡树的条件,但红黑树的优势在于,能够在不超过3次的旋转下,维持树结构的类平衡,其性能是优于平衡二叉树的,参考红黑树。
4.B-Tree与B+Tree
红黑树依然存在的问题是,在数据量非常大的时候,二叉树的度会非常大,磁盘IO的次数会增多,B-Tree的优势在于,每个节点是一块连续的物理区域,IO访问的效率非常高。B-Tree的劣势在于每个节点上都包含数据,一是会造成空间的浪费,二是数据之间不连续,对区间查询而言,不能访问连续的存储空间。B+Tree的叶子节点是逻辑连续的,能够支持区间查询。InnoDB在数据索引层面上只支持B+Tree,自适应hash是数据库本身的优化方案,不在索引考虑范围内。
2. InnoDB中的索引
2.1 聚簇索引(Clustered Index)
每个InnoDB表都有一个特殊的索引,称为聚簇索引,用于存储行数据。通常,聚簇索引与主键同义。为了从查询,插入和其他数据库操作中获得最佳性能,您必须了解InnoDB如何使用聚簇索引为每个表优化最常见的查找和DML操作。
- 当您在表上定义PRIMARY KEY时,InnoDB会将其用作聚簇索引。为您创建的每个表定义一个主键。如果没有逻辑唯一且非空的列或列集,请添加一个新的自动递增列,其值将自动填充。
- 如果您没有为表定义PRIMARY KEY,则MySQL会在所有键列都不为NULL的情况下找到第一个UNIQUE索引,InnoDB会将其用作聚集索引。
- 如果表没有PRIMARY KEY或合适的UNIQUE索引,则InnoDB在包含行ID值的合成列上内部生成一个名为GEN_CLUST_INDEX的隐藏的聚集索引。这些行由InnoDB分配给此类表中的行的ID排序。行ID是一个6字节的字段,随着插入新行而单调增加。因此,按行ID排序的行实际上在插入顺序上。
大意为 聚簇索引通常为主键索引(数值类型),没有主键时选择一个不为空的唯一索引(数值),如果没有找到合适的唯一索引,则InnoDB会在内部生成一个6字节长度的隐藏列,作为聚簇索引。
聚簇索引的数据结构为B+Tree,叶子节点为数据行,索引的结构如下图所示。
索引的建立过程会伴随着新的叶子节点加入,非叶子节点的修改,非叶子节点的合并/分裂,因此维护索引的成本是非常高的。索引带来的查询优势也非常明显,假设索引的节点是存储在磁盘上的,那么在对磁盘的常数次寻址就能够找到通过ID查询的数据,如对ID为55的数据,在进行三次磁盘寻址操作(连续访问效率非常高),就能够找到对应的值;在对ID进行区间查找时,也只需要进行三次寻址操作。
2.2 辅助索引(Secondary Index)
很多时候基于ID的查询不能满足业务场景,此时需要增加辅助索引,或称二级索引,除了聚簇索引外,其他的索引都称为辅助索引,辅助索引的也是一棵B+Tree,不同的是,叶子节点存储的不是数据,而是聚簇索引,如下图所示。
辅助索引的搜索流程为,先根据辅助索引查到聚簇索引,再根据聚簇索引查到原数据行(有些情况下不需要回表,如覆盖索引)。
2.3 创建索引的原则
索引在可以理解为数据写与数据读的效率博弈,在建立索引时,可以很大程度上提高查询效率,大部分的应用系统符合28规律(2分写8分读),读的效率大多是系统瓶颈,因此可以根据不同的业务场景或查询条件,添加一个或多个辅助索引。
2.3.1 辅助索引是不是越多越好
辅助索引在提高查询效率的同时,会占用较多的存储空间,当新增/修改/删除/修改表结构时,要维护相应的索引(新增/合并/分裂等),索引非常多的话,更新数据的代价是非常大的,目前没有权威资料证明每张表创建索引的上限是多少,但一定要依据自己当前系统的负载能力以及业务场景适当创建索引。
2.3.2 哪些字段适合做索引
最好选择数值型字段建立索引,数值型占用空间更少,且对比速度更快,若必须使用其他类型字段建立索引,该字段最好是有序的,如日期类型,有序的字符串等,最不理想的索引值:uuid,完全随机的字符串,无规律,占用大量的存储空间但无法提高查询效率。
2.4 索引的匹配规则
以表 t(id, a, b, c, d)为例
2.4.1 等值匹配
聚簇索引的等值匹配:select * from t where id=1;
辅助索引为index(a, b): select * from t where a=1 and b=2;
2.4.2 区间匹配
聚簇索引的区间匹配: select * from t where id>1 and id<10;
辅助索引为index(a, b): select * from t where a>1 或 select * from t where a=1 and b>1
注意:当辅助索引为多个字段的组合时,当组合中的某个字段使用了区间条件时,后续字段(如果有)的查询条件无法匹配索引,这种限制是由于B+Tree的结构导致的,所有的数据都保存在叶子节点中,当使用了某个字段的区间索引,并且查询条件中存在后续字段时,B+Tree无法从所以中查询连续的数据,估索引失效。
2.4.3 最左列匹配
辅助索引为index(a, b): select * from t where a=1 and b=2匹配索引;select * from t where a=1 匹配索引;select * from t where b=2无法匹配索引。
注意:当辅助索引为多个字段时,只有按照顺序的等值匹配可以匹配索引,原因同样是B+Tree无法查询连续的数据。
2.4.4 最左匹配
假设字段c为字符串 辅助索引为index(c):select * from t where c like '123_' or c like '123%';like条件的通配符必须在右侧时索引才能匹配索引,原因依然是B+Tree的叶子节点是连续且有序的,查询条件无法查询连续的数据。
2.4.4 覆盖索引
辅助索引为index(a,b,c) :select a,b,c from t where a=1,b=2,c=3;辅助索引的叶子节点中包含了查询条件中的所有数据,故不需要回表,可以将结果直接返回。
2.4.5 表达式
辅助索引为index(a):select * from a where (a+1)>3;表达式条件无法匹配索引。
2.4.5 IN/Exist
这两种操作可以转化为等值匹配,在存在索引的情况下回匹配索引。
3.总结
本文浅析了Innodb聚簇索引与辅助索引的结构以及查询流程,基于B+Tree的innodb索引能够很大程度上提高查询效率,但创建索引需要一定的知道原则,索引的匹配也需要符合一定的规则。