对Mysql索引的理解

165 阅读11分钟

引言

对于Mysql索引,相信大家都很熟悉,不过是在日常项目的开发,还是在面试中,索引都是非常重要的一部分。本篇文章将会结合实际测试来阐述我个人对索引的理解。

概述

简单的来说,索引是一种帮助数据库快速获取数据的一种数据结构。一般来说,索引文件中会存储数据表中索引字段的值或其它数据(根据存储引擎和索引类型来决定),所以说索引文件也会很大,不可能完全存储在内存中,往往存放到磁盘中。

另外,并不是所有情况都适合建索引,首先我们插入和修改数据时,也需要对索引文件进行修改,所以对于需要频繁修改数据而对查询性能要求并不高的表不适合建立索引。另外,索引文件会占用额外的空间,滥用索引会对存储空间造成浪费。

接下来,我将从索引几种不同的分类方式来深入的理解索引。

从物理存储角度分类

我们根据索引文件中是否存储着所有数据作为区分,将索引分为聚集索引(又称聚簇索引)和非聚集索引。对于聚集索引来说,索引文件中不仅仅存储着索引字段的值,同时存储着索引表中其它字段的值;对于非聚集索引来说,索引文件中存储的仅仅是索引字段的值已经指向记录的值(如主键ID或记录存储的地址)。

我们拿最常用的两种存储引擎InnoDB和MyISAM来分析。

在MyISAM存储引擎下,是没有聚集索引的,他的所有索引最终存储的都是索引字段的值和数据存储的地址,所以其都是非聚集索引。

在InnoDB存储引擎下,每张表都会有一个聚集索引,即主键索引。值得注意的是,即时我们没有为数据表创建主键索引,InnoDB会通过一个隐藏的_rowid字段来作为主键(具体可参考),所以说,我们最好还是要自己创建一个主键。 而对于其它所有非主键索引来说,都是非聚集索引,其存储的是索引字段的值和主键ID的值,那这里就引出了索引覆盖和回表查询的概念。

索引覆盖

简单的说,当我们利用索引进行查询时,如果我们查询的字段仅仅是索引字段和主键字段,那么我们只从当前索引文件上就能获取所有我们需要的数据,这就叫索引覆盖。

回表查询

与索引覆盖相反,如果我们查询的字段不仅仅是索引字段和主键字段,那仅通过该索引文件我们是不能获取到所有我们需要的字段的,这时候我们就需要通过该索引文件获取到我们所查询记录的主键ID,再回到主键索引中查询我们需要的数据,这就叫回表查询。

我们很容易就可以得出结论,索引覆盖的效率是要比回表查询高很多的,所以日常开发中我们应该尽量避免回表。接下来我讲用一个简单的测试来验证下我们的结论。

表结构如下,我们为age字段设置了索引。表中有500万条数据。 image.png 我们首先测试下需要回表查询的情况,我们查询age=59的记录的age和name两个字段。

image.png 我们再测试不需要回表即索引覆盖的情况,我们同样查询age=59的记录,这次我们查询age和id两个字段。

image.png 通过上面的测试我们能很明显的发现,索引覆盖的效率是要比回表查询高很高的。

另外,Mysql5.6后引入索引下推减少了回表次数,具体可查看该文章

从数据结构角度分类

哈希索引

哈希索引是通过散列表来实现的,其类似与JAVA语言的HashMap,他会通过哈希函数计算出一个哈希值来映射到散列表的相应位置,如果发生哈希碰撞,则会以链表的形式,存在同一个哈希桶中。目前,只有Memory存储引擎支持哈希索引,另外,InnoDB存储引擎支持一种自适应哈希索引(后面会介绍到)。

对于哈希索引来说,其优点是当我们采用索引的等值查询时,其效率可以达到O(1),但是其缺点也是显而易见的。

  • 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。
  • 哈希索引只支持等值查询,并不支持范围查询。
  • 如果存在较多的哈希碰撞,哈希索引必须去遍历链表,效率会受到影响。

B+树索引

对于B+树索引来说,我们应该理解为什么要采取B+树这种数据结构,我们首先从二叉搜索树开始聊起。

二叉搜索树

二叉搜索树(又称二叉排序树)是一种二叉树,它的每个节点的左节点永远都比自己小,右节点反之。并且它的每个节点的数据只有一个。所以说,二叉搜索树的弊端是显而易见的。

  • 二叉排序树是不平衡的,最坏的情况会退化为一个链表。
  • 二叉排序树每个节点只能存储一个数据,而一个数据表中可能会有成百万条数据,如果采用二叉排序树那么会时二叉树的高度很高,而搜索一层就会进行一次IO操作,这种效率是很低的。

为解决二叉排序树的平衡性,又出现了平衡二叉树红黑树,但是他们依然是二叉树,并不能解决高度过高的问题,我们引入了2-3排序树(每个节点可以存储两个数据),并在其基础上引入了B树。

B树

B树又称B-树,全称为多路平衡查找树。B树具有以下特点:

  • B树的一个节点可以存取多个数据。由于磁盘块和页的大小一般情况下为4KB,所以在B树中一个节点的最大存储数据个数的总大小一般不能超过4KB。
  • B树的所有叶子节点都在同一层
  • B树至少有3个节点(如果有一个孩子,就是不平衡的树,如果没有孩子,那就是一个节点。)
  • B树通过通过分裂来保证树的平衡,如果节点中的元素大于等于m,就会进行分裂。如果一个子节点中的元素个数小于m/2,其兄弟节点的元素个数大于m/2,那就将兄弟节点中一个元素移到父节点,父节点中一个元素移到相应子节点。
  • B树的所有节点都会存取数据信息。 所以说,B树解决了二叉搜索树不平衡核高度太高的问题,但是B树的所有节点都会存取数据信息,而磁盘每一页的大小是固定的,存取数据就导致每一页上存取的记录数量就会减低,从而使树的高度变高,基于此,我们引进了B+树。

B+树

B+树在B树的基础上作了改进

  • B+树的非叶子节点上只存储索引字段的值,而所有数据都存储在叶子节点上,这样会使非叶子节点上每个节点存储的数据个数增多,从而使树的高度下降,以减少IO操作;
  • B+树的叶子节点之前通过链表相连,如果扫描数据的话,B+树只需要扫描叶子节点就好了,而B树需要全树扫描。并且B+树的排序能力更强。

自适应哈希索引

自适应哈希索引是InnoDB存储引擎独有的一种机制,它主要是用于优化辅助索引(二级索引)的一种机制。当我们通过辅助索引查找数据时,我们需要先通过辅助索引找到记录的主键,再通过主键到主键索引中找到我们想要的记录。而如果一个辅助索引的记录频繁被访问,那么每次都会通过B+树查询一次,无疑会对资源产生浪费,而自适应哈希索引就是用来解决这一点的,它会将索引的字段作为键,将数据所在页的指针作为值,等到下一次查询时,就可以通过哈希索引一步到位。

image.png 自适应哈希索引的条件:

  • 通过一种固定模式的查询语句访问该页100次。固定模式是指,如果我们的索引的联合索引,那么每次查询时筛选条件必须一致。
  • 页通过固定模式访问了N次,其中N=页中记录*1/16

自适应哈希索引的弊端:

  • 会占据InnoDB Buffer Pool
  • 自适应hash索引只适合搜索等值的查询,如select * from table where index_col='xxx',而对于其他查找类型,如范围查找,是不能使用的

从索引特性角度分类

主键索引(Primary)

对于InnoDB存储引擎来说,每个数据表中只能有一个主键索引,且主键的值是唯一的,不支持空值。

普通索引(Normal)

对于普通索引来说,这是最简单的索引,并没有什么限制。

唯一索引(Unique)

对于唯一索引来说,它的值是唯一的(如果是联合索引则是组合唯一),并且支持有空值(空值可以有多个)。

联合索引

联合索引就是多个字段组合的索引,它必须要满足最左匹配原则。

最左匹配原则

假设我们现在有A,B,C三个字段,我们按照A,B,C的顺序建立一个联合索引,那再数据比较时,会首先按照字段A进行排序,字段A相同时再按照字段B排序,字段B相同时再按照字段C排序,这就是最左匹配原则。也是因为这个特性,如果我们只通过字段B或字段C进行查询,那么就会造成索引失效。 接下来我们通过一个测试来直观的看一下最左匹配原则。依旧是上面的表结构:

image.png 我们将phone、email、qq字段建立联合索引,并命名为test

image.png 首先我们按照正确的顺序进行数据查询,并通过explain进行判断索引是否失效。我们可以看到possible_keys和key中索引是正确被使用的。

image.png 然后我们再将顺序修改一下再进行测试。我们发现索引依然是正确被使用的。那为什么我们没有按照正确的顺序进行查询索引还能正常使用呢?这是因为mysql底层会帮助我们对sql进行优化。

image.png 接下来我们只保留phone字段的查询条件。索引依然是可以使用的。这里虽然我们只查询了phone字段,但是phone字段在索引的最左侧,依然满足最左匹配原则。

image.png 接下来我们只保留email和qq字段的查询条件。可以发现此时索引已经失效了。 image.png 所以说,在我们在通过联合索引进行查询时,一定要让其满足最左匹配原则。同时在建立索引时,应尽量让最可能被查询的字段放到最左侧。

全文索引(FULLTEXT)

全文索引主要用于在文本字段中的相似度匹配,在Mysql5.6以前的版本,只有MyISAM支持,在Mysql5.6及以后,InnoDB引擎中也支持。而在实际的业务开发中,我们大多使用搜索引擎进行文本相似度匹配,所以全文索引的使用场景相对较少,这里不再进行介绍。