MySql(一)——索引

前言

这里不会总结SQL的语法,不会总结SQL的实例,只是总结MySQL在实际工作中的性能优化部分。提到MySQL查询优化部分,开门首先要面对的就是索引的知识和概念。之前零零散散看了一些,但是都没有总结成笔记,这里就开始对MySQL更高级的应用进行梳理,就从索引开始。

索引是什么

如果说每次提到索引,我们只能说出这个就是书籍中的目录,这个理解似乎依旧很浮浅,这种只是说对索引的作用很了解,但是对索引具体内部结构可能并不是很了解。真正的索引可以如下定义:

索引——为了加速对数据表中数据行的检索而创建的一种分散存储的数据结构。(对于分散存储与连续存储数据结构,这个概念其实是数据结构的基础,这里就不多解释了)。其实从总体上来看,索引的作用还有以下几个作用

1、能极大的减少存储引擎需要扫描的数据量(目录的作用既是如此)。

2、能将随机IO变成顺序IO(由于数据大概率是分散在磁盘空间存储的,通过顺序IO读取数据效率会高的多)

3、可以帮助我们在进行分组、排序等操作的时候,避免使用临时表。

这几点会在这一系列博客总结完成时,都会有所说明。其实,现在问大部分程序员:MySQL的索引数据结构是什么,大部分都会答出是B+树,但是MySQL为啥会选择B+树作为自己的索引结构呢?这就得从基础的数据结果说起。

从简单的数据结构说起

从二叉查找树说道B+树。至于什么是二分查找这种基础概念,就可以翻翻数据结构了。

ps:一个将各种数据结构可视化的一个网站:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

基础的数据结构

二叉查找树

依稀记得大三,自己在备战研究生考试,在复习数据结构的时候,发现其实所谓的二叉查找树和二分查找其实就是对同一个查找方式的不同表达形式而已。(如果不懂的朋友,可以先看看什么是二分查找)

上图是一个二叉查找树,其实就是对顺序二分查找的一个树形表达,可以看到,如果在所给的顺序中查找9这个数值,会发现二分查找和在二叉查找树中查找,其实每次对比的元素是一样的(也就是说,查找过程是一样的)。这个是数据结果的基础知识,这里不再讲述过多。大学的数据结构中还提到,二叉查找树的查询时间复杂度与二叉查找树的层高成正比,这个也造成了二叉查找树的缺点很明显,如果二叉查找树中的元素本身都是有序的(如下所示),就有点可怕了。

这个时候在所谓的二叉查找树中查找元素,和遍历有什么区别呢?并没有,所以为了优化这个问题,衍生出了很多其他的数据结构。

AVL树

通过各种旋转操作,可以将二叉查找树变成AVL树(怎么旋转,这个就百度吧,并不是很复杂),AVL树的定义是每个节点的左右子树层高差的绝对值要小于1,如果大于1则需要进行旋转操作。

 按照道理来讲,AVL树已经较好的解决了查找的时间效率问题,毕竟已经解决了层差太大的问题,但是为什么MySQL没有采用这种结构呢?这是因为分支的缘故(这里的分支,其实就是数据结构中的度的意思。)如果用AVL树作为索引,估计是下面的样子

 问题其实也很明显:试想一下,如果有上千万笔数据,这个AVL树的层高会有多少呢?(log2N<总的数据条数,这个不等式解出来的N,即为AVL的最小层高)。但是可以想象,这个层高并不低。所以这个弊端就是:由于节点度数太少,导致最终树的层高会很高,比较的元素次数其实依旧很多——它,太深了

B树

如果想要知道什么是B树,这个还是百度吧,已经有很多博客介绍的很详细了,这里先录入一个动图(其实就是二三树,某一种意义上来说二三树是B树的一个特例)

 可以看到,同样的数据,B树的层高比AVL树的层高已经矮了很多(因为增加了节点的度)

如果用B树作为索引,其实很大的可能会如下所示:

 这个时候,如果需要查找8这个数据原数,需要加载的磁盘块比AVL树要少的多,同时只用了3层,存储的数据比AVL树多了不少。那为什么MySQL还是没有选B树作为索引结构呢?其实还有一个问题:每个非叶子节点,都保存了数据,每次读取数据的时候,依旧加载从根节点到目标节点所有的磁盘数据。而B+树正好可以解决这个问题。

为什么是B+树

走到这里,其实对于为什么是B+树有一个大致的答案了,因为B+树刚好能较好的解决层高过高,数据IO加载过多的问题。

这里不再使用动图,直接贴出最终的结果图:

其实B+树只是在叶子节点的下一层,维护了一个链表,这个链表顺序保存了所有的节点。非叶子节点并不保存数据,只是维护子节点的索引。

相比B树,B+树在具备了B树的优势的同时,其实还有以下几点自身的优势:

1、B+树扫库、 表能力更强(相对AVL树而言)
2、B+树的磁盘读写能力更强,非叶子节点不用加载磁盘数据,最终只需要加载叶子节点对应的磁盘数据即可。
3、B+树的排序能力更强,叶子节点自动维护有序的链表,同时因为这个链表结构,某种程度上将随机IO变成了顺序IO。
4、B+树的查询效率更加稳定(这个姑且算是一个优势吧),每次查找都需要找到叶子节点,对数据量较大的情况下是有优势的。

所以分析到这里。我们基本上能完整的回答,为啥MySQL用B+树作为索引了。下面我们就继续总结各种索引,及其使用原则。

各种索引

进入到指定数据库的存储文件的文件夹中,其实可以看到mysql所有数据的存储文件。mysql中存储引擎其实是可以设置在表级的,这里先简单说说InnoDB和MyIsam索引的区别

InnoDB与Myisam索引的区别

1、两者的存储方式不同

myisam引擎的索引是单独存放在另一个文件中,innodb的索引是和数据存放在一起,如下图所示。

二者的辅助索引机制不同。

myisam的辅助索引,最终的叶子节点也是指向实际的行数据,而innodb的辅助索引叶子节点指向的是主键索引,如下图所示

左边是InnoDB索引的检索过程,右边是MyIsam引擎索引的检索过程。InnoDB这样设计的初衷是:如果数据迁移或者变更了,只需要重新维护主索引即可,不需要再去维护辅助索引了。反观MyIsam,如果数据变更了,需要同时维护主索引和辅助索引。

具体可以参看其他大牛的博客: MyIsam和InnoDB索引的差异

聚集索引

百科上的定义为:数据库表行中数据的物理顺序与索引的顺序相同。根据实例来理解,从上面的InnoDB存储引擎的索引来看,主索引就是聚集索引,辅助索引就是非聚集索引。由于一个表中的物理顺序只有一种,因此对应的聚集索引也之后一个(即主索引只有一个)。

联合索引

对多个字段同时建立的索引即为联合索引,为了下面表述方便,我们用index来表示索引。index(column01,column02)即为一个联合索引,在column01和column02两列上建立索引,因此成为联合索引。index(column01)即为单例索引。因此某种意义上说单例索引其实是特殊的联合索引。

最左匹配

实例:如果某一天,发下一下两个SQL查询很慢,需要建立索引进行优化,我们该如何建立?

select * from user where name = ?

select * from user where name = ? and phoneNum = ?

经常会有些简单粗暴的操作,建立两个索引 

create index index_user_name on users(name);
create index index_user_name_phone on users(name,phoneNum);

但是由于存在最左匹配原则,建立的第一个索引其实是多余的,因为进行最左匹配的时候,select * from user where name = ?也会命中我们建立的联合索引。如果我们使用select * from user where phoneNum = ?则不会命中任何索引。

覆盖索引

如果一个索引包含(或覆盖)所有需要查询的字段的值,称为‘覆盖索引’。如果命中了覆盖索引,由于我们需要的字段值,其实就作为了关键字存储在了B+树的非叶子节点中,因此命中了覆盖索引,不需要再去加载叶子节点的磁盘空间了。

还是以上面的最左匹配原则的实例来说明,如果在建立了联合索引index(name,phoneNum)之后,我们进行以下查询,则就可以称我们建立的索引为覆盖索引了。

select name,phoneNum from users where name=? and phoneNum = ?

这里也是有时候某些公司的开发规范中会让我们禁用select * 的原因。命中了覆盖索引可以减少IO操作,将随机IO变成顺序IO。 

索引的一些原则

数据的离散性

在总结索引的一些原则的时候,我们先介绍一下数据列的离散性这个概念。数据列的离散性,从某一种层度上,我们可以直接理解为数据的重复性,离散性越高,数据的选择性就越好。这样听起来还是有点蒙圈,直接用实例来解释。有如下一个B+树

上述的结构只有0和1两种数据(类似表中的性别字段)。如果要查询这个结构中为0的节点,其实和遍历似乎没有多大区别,这就是数据离散性不高的提现。MySQL在查询优化阶段如果发现要查找的数据离散性不高,会放弃使用索引,这一点我们在后面再介绍。 

联合索引列选择的原则

1、经常用的列优先(尽量满足最左匹配原则)

2、选择离散性高的数据列(尽量满足高离散度)

3、宽度小的列优先(尽量让索引中非叶节点的度数很高)

通常来说这些原则的重要程度从1到3依次递减。

可以理解为一些规范性的东西

1、作为索引的数据列,长度约短越好(提高非叶节点的度,降低B+树的层高)

2、索引不是约多越好,而是约合适越好(例如联合索引中最左匹配的实例)

3、like xxx%可能用到索引(只是可能,如果针对离散性较差的列,mysql会在SQL优化阶段会自动判断不使用索引,例如如果对性别列建立了索引,使用like ‘男%’ 作为查找条件,并不会用到该列的索引),like %XXX和like %XXX%,绝对不会用到索引了。

4、where条件中的not in 和不等于(<>)不会用到索引,因为选择比较多样,查询在遍历非叶子节点的时候,不知道遍历左子树还是右子树还是中间的子树。

5、范围(大于,小于,区间),order by查询的时候,会用到索引。因为这个条件并不会像不等于一样,让查询遍历时不知道选择哪条子路径

6、联合索引中,如果不是按照索引最左列开始查找,则无法使用索引。例如:在之前的实例中,建立了index(name,phoneNum)之后,我们如果使用select * from users where phoneNun = ? and name = ?则并不会命中索引。

7、联合索引中,精确匹配最左列,后续列使用返回条件,也是可以命中索引的。例如:select * from users where name = ? and phoneNum > XXX,这个是可以命中index(name,phoneNum)的。

8、联合索引中,如果查询有某个列的范围查询,则其右边的所有列都无法使用索引了。例如:存在一个索引 index(name,age,phoneNum),如果使用如下SQL,select * from users where name = ? and age >? and phoneNum=?,则无法命中该联合索引。

总结

本篇博客在参看了相关大牛博客的基础上,总结了MySQL中的索引,梳理了一下为何MySQL使用B+树作为自身索引结构的原因,同时也针对联合索引,覆盖索引,聚集索引通过实例进行了说明,并总结了一些规范化的操作。

发布了129 篇原创文章 · 获赞 37 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/102996929