MySQL索引

88 阅读8分钟

大家好,今天我给大家讲讲关于MySQL数据库索引相关的一些内容,会涉及到一些原理性的东西,如果有错误,欢迎批评指正,共勉。

讲解内容

图片.png

什么是索引?

我们一直在说索引索引,这到底是个什么东西?我们在阅读一本书的时候通常会看他的目录有些什么内容,通过目录可以快速定位到我们感兴趣的知识点,这是不是比一页一页翻速度快很多,类似于目录的东西我们就叫做索引。
在数据库中,动不动就是几千万条数据,通过索引,我们可以大大降低查询的时间。这也是通常招聘要求类似于一些 "了解SQL优化,数据库原理" 等的一部分内容。同样一条SQL语句写出来,有的查询时间可能是10多秒(这是不能忍受的),但是如果了解索引等相关概念,有的查询时间就有可能降低几千倍。下面我们就慢慢了解吧!

索引中常见的数据结构模型

哈希表

哈希表:这是一种插入和查找时间复杂度都为O(logN)的数据结构,大部分语言都提供了对哈希表的实现,比如Java的HashMap,高性能的键值对,通过对键进行哈希函数计算,将值映射到指定的区域,取的时候可以对键再次进行Hash,然后得到结果就可以到这个区域进行取数据。
不难想到,无论你的哈希函数设计的有多好,都有可能产生哈希碰撞,那么如何解决这个问题呢?

  1. 拉链法,这是MySQL数据库中使用的,将冲突的键所对应的值通过一根单链表进行存储(长度太长也不行,Java中是转换为了红黑树,以致于不让查找退化为线性时间)。
  2. 开放定址法(线性探测法):f(val) mod {1,2,3....len-1},冲突了怎么办,一次选择集合中的树,线性的寻找下一个空的位置,因此开放定址法也称为线性探测法,那么你可能会有个问题,我集合内只能是递增序列吗?如果在我前面才有空位置怎么办?所以你可以使用{1^2,-1^2,2^2,-2^2....}这种我们称为二次探测法,也可以通过随机数生成,称为随机探测法。
  3. 再哈希法:碰撞了怎么办?顾名思义,我准备n多个哈希函数,换一个哈希函数再试。
  4. 建立公共溢出区:将碰撞的数放入另一块空间,查找的时候判断是否相等即可

有可以O(1)时间复杂度寻找到指定数,缺点就是不能范围查找必须得遍历哈希表。

有序数组

这个就比较简单了,我把数据按照顺序进行排列,然后使用二分进行查找。
可以按范围查找,也可以O(1)时间复杂度寻找到数据,那这个数据结构是不是很好啊?他也有缺点,就是你删除/删除会让后面的数据挪动,这个时间复杂度在O(N)。

B+数

B+树是在二叉搜索树的基础上演变而来,将有序数组按照树形结构组织,小的在根节点左边,大的在根节点右边,但是也有一个问题,通常数据量很大的时候比如1000万,全部加载进入内存,这个占用的空间大小不是可忍受的,然后试图将其存入硬盘,但是这又带来一个问题,磁盘的读写速度比内存的低几千倍,一次IO时间大概在10ms,如果树高为100,那么100次IO带来1s时间损耗,这顶不住啊,所以尝试将二叉树变为多叉树,一个节点也可以存储多个数据,降低树的高度。这样就出现了B树的概念,后来又在B树的叶节点上进行双向链表操作,让其支持范围查找,就出现了B+树。

B+树在MySQL有举足轻重的作用,如果你有时间的话,可以自己尝试实现一下B+树,加深对于B+树的理解,这对于后续学习和优化SQL都有帮助的。

Innodb选用的数据结构模型

哈希表:只能用来做等值索引,一些模糊查找就不支持,比如查找一个字符串'abc',可以直接找到对应的位置。在innodb存储引擎中,哈希索引是自适应的,不需要DBA去进行管理的。

B+树索引
聚簇索引(clustered index),对于MySQL中的一张表,对于主键默认会创建一个聚簇索引,会将所有的列数据存放在叶子节点中,非叶子节点中只存储范围信息。
非聚簇索引(secondary index也叫辅助索引),叶子节点中并不会存储全部数据,只会保存创建辅助索引列的数据以及对应的主键id,如果使用到了辅助索引,但是想要的数据没有,这就需要再根据主键去聚簇索引中再搜索一次,这个过程我们称为回表,其他结构和聚簇索引类似。

对于辅助索引,创建多个列我们称为联合索引,其中保存了多个列的数据。

索引优化

上面都是介绍了一些基础概念,我们真正需要了解的是下列这些优化手段,但是了解下列这些优化手段又需要上述知识来做铺垫。

覆盖索引

如果我们要想查找的数据,在叶节点中全部都能找到,而不需要回表的情况我们称为覆盖索引,这减少了一次树的搜索过程,在数据量大的情况下,性能提升还是非常明显的。

最左前缀原则

对于联合索引 idx_abc,我们对a,b,c列创建了索引,且顺序是abc,如果查询的条件是按照从a开始,顺序使用索引,我们就可以利用上这个联合索引快速找到我们想要的数据,但是如果你不按顺序使用比如ac,那么就只能使用索引a,使用不了索引c,这是为什么呢?因为节点中的列是全局有序而局部无序的。

(1,2,1),(1,2,2),(1,5,1) (2,4,1)(2,6,3)
你会发现11122是全局都有序的,索引我们从a开始使用索引是可以的,然后开始顺序使用b索引225,46你会发现这些是局部有序的,且基于前一个索引a,才能达到这种局部有序。
所以你直接ac,是达不到全局有序的,你搜索到了a,然后又想去使用c,121,他并不是有序的所以使用不到索引二分。

小节,我们创建索引一定要根据具体的业务逻辑上,最左前缀原则表明了你能不能使用索引中的多个列,使用的列越多,你查询的速度就越快。

索引下推

这个功能是MySQL5.6才推出的。
如果我们在索引中找不到想要的数据时,就会根据主键id进行回表查询。那么如果我有一个索引idx_ab,和一个查询条件

select a,b,c from t where a like '张%' and b =2 and c=1

这里加入索引寻找到姓张的有4条记录,然后c在索引中没有,就会回表去查询这5条记录(并不会去看b的值),索引下推是根据索引中条件直接对不满足的记录不回表,比如这里4条记录需要回表4次,这里我可以根据b列提前判断,如果满足b列的只有两人,那么就可以减少回表两次(也就是减少两次树的搜索)。

全文索引

是对于 a like '%张%'在所有记录都能搜索到的技术。之前的MySQL是不支持的,但是最新的支持。这并不是面试的重点,因为有别的引擎专门做这个,比如es。

索引失效

图片.png
其中几个重点

  1. 使用函数对字段进行计算,会让索引失效(类型转换也是隐式调用函数,以及运算符运算)
  2. 使用or运算符,但是并不是只要用到了or就会失效,比如有索引a,查询条件为a = 1 or b =2 这里的b不是索引列,就会失效。因为innodb知道a是索引列可以走索引,走了之后b始终要回表且全表扫描,这还不如直接全表扫描呢。
  3. 最左前缀匹配原则

小节

大概讲了一些跟MySQL索引相关的内容,知识面还是很多,可能不是很深入。但是基本原理都是如上所述,如果有兴趣可以再自己深入了解一下。

参考

《MySQL45讲》
《Innodb存储引擎》
《大话数据结构》