Mysql的索引优化与explain性能分析

1.mysql索引介绍

1.1 什么是索引?

Mysql官方对索引的定义:索引(Index)是帮助Mysql高效获取数据的数据结构。索引是一种排好序的快速查找数据结构,它的作用是排序和快速查找。

Mysql的索引优化与explain性能分析_第1张图片

比如,现在要找Col2=91的那一行数据,如果存在索引,拿91跟34比,比34大,就放到89这边,91又比89大,所以放到91这边,91配对成功,找到91指向的物理地址,返回数据,这样子有索引就不用去遍历整个表,再配对91,当然,如果遍历了整个b+树还找不到就返回Null。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。我们平常说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉树)结构组织的索引。

1.2 基本语法

【创建】

       create  [unique]  index    on  (colum1,...);

       或者

      alter add [unique] index on (colum1,...);

【删除】

        drop  index  [indexName]  on  tableName;

【查看】

        show  index  from  tableName;

1.3 索引原理

【初始化介绍】

一颗b+树,青色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1,P2,P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块,真实的数据存在叶子结点,即:3,5,9,10,13,15,28,29,36,60,75,79,90,99,非叶子结点不存储真实的数据只储存搜索方向的数据项,如17,35并不真实存在于数据表中

Mysql的索引优化与explain性能分析_第2张图片

【查找过程】

如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找,找到29结束查询,总计三次IO

1.4 什么情况需要创建索引?

【需要索引的情况】

1、主键自动建立唯一索引

2、频繁作为查询条件的字段

3、查询与其他表关联的字段,外键关系建立索引

4、查询中排序的字段,排序字段若通过索引去访问将大大提高效率

5、查询中统计或者分组字段

【不需要索引的情况】

1、经常增删改的表、频繁更新的字段(不仅要更新数据,还要维护索引树)

2、where条件里用不到的字段不创建索引

3、表记录太小,Mysql据说可以撑到300W条

4、某个数据列包含许多重复的内容,例如性别,都是男或者女,没必要建索引

Mysql的索引优化与explain性能分析_第3张图片

2.explain性能分析

再讲索引优化之前,需要先学会如何使用explain进行性能分析

2.1 什么是explain?

回首mysql的架构,在第二层有一个Mysql  Query  Optimizer,是Mysql官方提供的查询优化器,它负责优化select语句,为客户端请求的Query提供MySQL自己认为最优的执行计划(是mysql的观念,不是DBA的想法)。Explain关键字,可以模拟优化器执行SQL查询语句,从而知道Mysql是如何处理程序员的SQL语句的,分析查询语句或表结构的性能瓶颈,它的语法很简单:explain+sql语句,就如 explain  select  * from  t_dept

2.2 解读explain的信息

【id】

id是select识别符,也是select查询的序列号,包含一组数字用来表示查询中执行select子句或操作表的顺序

①id相同,执行顺序由上到下

Mysql的索引优化与explain性能分析_第4张图片

请记住,id的值一样,执行顺序是从上到下,上例的执行顺序便是:t1,t3,t2。虽然例子中sql语句的from后面的表顺序是:t1,t2,t3,但是mysql并不会按照这种顺序去执行,它通过查询优化器分析以后,认为t1,t3,t2的执行顺序更优良,从sql语句的角度分析,where条件最后的and是跟表t1有关,因此mysql会优先查询表t1的数据

②id不同,值越大优先级越高;若是子查询,id序号会递增,因此会被优先执行

Mysql的索引优化与explain性能分析_第5张图片

若explain分析得到的结果,id的值各不相同,那么值越大的,越先执行,因此上例中,表的执行顺序是:t3,t1,t2。从sql语句来看,查询表t2需要先查询表t1,而查询表t1又需要先查询表t3,或者这样子看,有括号的先查询,t2是在最外面,因此它最晚查,t1在里面,t3在最里面,所以t3最早查。

③id既有相同的又有不同的

Mysql的索引优化与explain性能分析_第6张图片

这里先解释一下,为什么sql语句select  t2.*  from (...) s1。from后面不是跟着表名而是跟着一个子查询?意思是把这个子查询的结果当做一个虚表,为这个虚表命名为s1,然后再从s1中查找数据,虚表有一个官方称呼—衍生表。若分析后的结果,id的取值有相同的也有不同,像上例的1,1,2。这时候,相同id的为一组,按照从上往下的顺序执行;不同组间,id值大的优先级高,先执行。例如1,1为一组,2单独为一组;然后看id的值,id=2的值大,因此它先被执行,然后执行1,1这组,由于这一组id的值相同,因此是按从上往下的顺序执行,总的顺序是: t3,,t2。从sql语句来看,先执行括号里面的即先执行t3,t3执行完以后衍生出一个虚表s1,然后再执行这个虚表和t2,所以才会得到:t3,,t2的执行顺序。derived是衍生的意思,即sql语句中的s1,表示衍生表。derived2中的2,表示这个衍生表是由id=2执行的那个表(即t3)衍生出来的

【select_type】和【table】

table,指明这一行的数据是关于哪张表的

select_type,指明数据查询时的操作类型,主要是用于区别普通查询、联合查询、子查询等复杂查询,有6种取值:

①simple,简单的select查询,查询中不包含子查询或者Union

②primary,查询中若包含任何复杂的子查询,最外层查询则被标记为primary

③subquery,在select或where列表中包含了子查询

④derived,在from列表中包含的子查询被标记为derived(衍生)mysql会递归执行这些子查询,把结果放在临时表里

⑤union,若第二个select出现在Union之后,则被标记为union;若union包含在from子句的子查询中,外层select则被标记为derived

⑥union result,从union表获取结果的select

【type】

type,字面意思是访问类型排列,表示SQL语句的查询效率,常用的取值有:

性能从最好到最坏依次是: null > system > const > eq_ref > ref > range > index > all

 一般来说,得保证查询至少达到range级别,最好能达到ref!!!

①system

system类型表示:表里面仅有一条记录(相当于系统表),是const的特例,平常几乎遇不到,因此可以忽略。

②const

const类型表示:表最多只有一条匹配行,表示通过索引一次就找到数据,常用于主键或unique索引,因为只匹配一行数据,所以很快

当在查询select * from t1 where id=1时,由于是用主键来查询的,主键值是唯一的,所以表中有且仅有一条数据与之匹配,这时候Mysql就能将该查询转为一个常量,则type=const,将查询后的结果当成衍生表d1,由于查询结果只有一个,所以d1只有一条记录相当于系统表,此时查询,type便为system类型

③eq_ref

eq_ref是多表联接唯一性索引扫描,对于前表的每个索引键,后表只有一条记录与之匹配,常见于主键或唯一索引扫描,eq_ref可用于使用=比较带索引的列

比如,t1是员工表,t2是部门表,t2的主键id是t1的外键。 id值若相同,按从上往下的顺序查询,因此先查询部门表t2,对t2的查询方式是全表扫描,找到t2所有的部门id,然后对员工表t1查询,找出与前表(t2表)找到的id匹配的员工,当此时t1只有一条记录时,type的值便是eq_ref。换句话说,当找的部门是研发部时,会有多条记录,因为研发的员工有多个,当找的是总裁部时,仅有一条记录,因为总裁只有一个。

④ref

ref区别于eq_ref,因为ref是非唯一性索引扫描,它返回匹配某个索引值的所有行,它可以找到多个符合条件的行

Mysql的索引优化与explain性能分析_第7张图片

当没有建索引去查询col1='ac'的表数据,type的类型为all,只能是全表扫描。当建立了索引:create index idx_col1_col2 on t1(col1,col2),再去查询col1='ac'的数据,mysql用索引去查找,且col1字段并不是表t1的主键,没有唯一性,查出来的数据会有多条,此时的type的类型就是ref。能达到ref级别,sql优化就已经很不错了

⑤range

只检索给定范围的行,使用一个索引来选择行,一般就是where语句出现了between<>in等查询。这种范围扫描索引比全表扫描好,因为它只需要开始于索引的某一点,结束于另一点,不用扫描全表

⑥index

index与all区别在于index类型只遍历索引树。这通常比all快,因为索引文件通常比数据文件小,虽然all和index都是读全表,但是Index是从索引中读取的,而all是从硬盘读取的

⑦all

all类型会遍历全表找到匹配行,不使用索引,就只能全表扫描了

【possible_keys】和【key】

possible_keys,指明执行SQL语句时,理论上需要用到的索引列表,若查询涉及到的字段上存在索引,则该索引被列出,但不一

                             定实际使用

key,表示实际使用的索引,如果为Null,则没有使用索引,若查询使用覆盖索引,则该索引仅会出现在key列表中

 (覆盖索引:select查询的字段正好和创建索引时用的列一致)

例如:

理论上可能会用到主键索引和idx_t1索引(观察possible_keys),但实际上只用到idx_t1索引(观察key)

理论上没有用到索引(possible_keys为null),但实际上用到idx_col1_col2索引,这就是覆盖索引。查询的字段是col1、col2,正好与创建的复合索引idx_col1_col2的列和列顺序一样,所以只显示在key列表中,possaible_key显示null

【key_len】

key_len指明索引中使用到的字节数,通过该列计算出查询时使用的索引长度

Mysql的索引优化与explain性能分析_第8张图片

当查询条件col1='ab',用到的索引字节数为13,当加了一个查询条件,col1='ab' and col2='ac',用到的索引字节数为26。当查询结果一样的前提,key_len越小越好,但是这是不可能的。想让马跑的远,又不给马吃草,这是不符合逻辑规律的。当查询条件越多,用的key_len就会越大,得出的结果就会越精确。

【ref】

ref,表示哪些列或常量被用于查找索引列上的值。如果使用的是常数等值查询,ref会显示const,如果是连接查询,ref会显示驱动表的关联字段,如果使用表达式或者函数,或条件列发生了内部隐式转换,ref可能显示为func

Mysql的索引优化与explain性能分析_第9张图片

d值相同,执行顺序由上至下,此条SQL的表查询顺序是t1,t3,t2。

查询t1时,ref=const,const就是常量的意思,结合sql语句t1.othe_column=''是匹配一个空字符串来查询数据,空字符串就是一个常量;查询t3时,ref=test.t1.ID,意思是用的列是test数据库的t1表的ID列,结合sql语句...and t1.id = t3.id,以t1表为驱动表,来查找t3表中符合条件的数据;查询t2时,ref=test.t1.ID,意思也是用test数据库的t1表的ID列,因为查询t2的sql语句为 where t1.id = t2.id,跟查询t3表的语句时一样的。

【rows】

rows,根据表统计信息及索引选用情况,大致估算出找到所需记录要读取的行数

Mysql的索引优化与explain性能分析_第10张图片

当没有创建索引,查询t2表的type类型是all,说明是全表扫描,Mysql估算出需要读取640行才能得到sql语句想要的结果。用了索引后,mysql估算出可能要读取的行数是142行

【extra】

extra,包含不适合在列中显示但十分重要的额外信息。最重要的3个取值: using filesort、using temporary 、using index

①using filesort

说明这条sql不是按照表内的索引顺序排序,而是用了一个外部的索引排序,Mysql中无法利用索引完成的排序操作称为"文件排序",即filesoft。一般表现在带有order by等排序关键字的sql语句上,出现using filesort表示性能不好

Mysql的索引优化与explain性能分析_第11张图片

前面说道,索引的作用有2个:排序和查询。从key的值不为null,知道查询的时候用到索引,但是由于出现了using  filesort,同样也知道排序没用到索引,为什么会这样?仔细区分下第1条和第2条sql语句:前者是order by col3,后者是order by col2,col3,后者排序的字段正好与创建的索引列一致(col1用在搜索)mysql会用索引来排序,也就不会在内存花费时间和性能重新排序,就不会出现using  filesort。

②using temporary

出现using temporary,则Mysql要赶紧优化,性能极其不好。它表明MySQL在对查询结果排序时使用了临时表,常见于排序order by和分组查询group by

Mysql的索引优化与explain性能分析_第12张图片

临时表的创建很伤性能的,更不要拿它来排序,所以在排序的时候,排序的字段最好与创建索引用的字段极可能地保持一致(顺序和类型都一样)

③using index

using index的出现,表示效率不错,是良好的表现,它表示相应的select操作中使用了覆盖索引,避免访问了表的数据行, 若同时出现using where,表明索引被用来执行索引键值的查找,若没有同时出现using where,表明索引用来读取数据而非查找动作

Mysql的索引优化与explain性能分析_第13张图片

3.mysql索引优化

【最佳左前缀法则】

最佳左前缀法则:如果索引了多列,查询条件要从索引的最左前列开始并且不跳过中间的列!!

现在有一个学生表,该表的索引如下:

Mysql的索引优化与explain性能分析_第14张图片

注意红框标注的索引列,从上往下的顺序是name->age->sex,where语句后的查询条件,name一定要存在,就像:

从explain解析出来的信息,可以看到type是ref而且key不为null,说明建立的索引有用到,但如果where后name没用到,不论后面怎么搞,索引也用不到,所以说,索引的最左前列在where后一定要体现,否则如下:

第二点,where语句后的查询条件,不要name='' and sex = '',而把中间索引列age省略掉。当索引列都拿来做查询条件时,由type=ref知道肯定用了索引,从ref=const,const,const知道3个常量都拿去查询了

但是,如果中间去掉age的查询条件,由于name在,所以肯定是会用到索引,但是从ref=const可得只用到了一个常量,后面那个常量根本用不到:

【索引列不加操作】

不要在索引列上做任何操作(计算、函数、类型转换等),会导致索引失效。left(v,n)函数是Mysql自带的函数,意思是在指定的列v上,从左往右数起到n得到的值与给定的值相匹配的数据,可以看到查询的结果与...where name='张三'的结果是一样的

Mysql的索引优化与explain性能分析_第15张图片

但是,由于在索引列上加了其他操作,导致索引失效,用explain分析后发现,mysql居然是用了全表扫描来查询数据,所以,索引列就直接拿去查询,不要做过多的修饰和包装

【范围条件后索引列失效】

在where条件后,如果某列用了范围条件(如in,between,>,<等)则此列以后的索引列都会失效,mysql并不会再用索引去查询:

Mysql的索引优化与explain性能分析_第16张图片

当等值查询name='张三' and age=21时,因为用了2个常量,key_len=156;全值查询name='张三' and age=21 and sex='m',用了3个常量,key_len-=159;一旦在查询条件中,加了范围条件的查询,像name='张三' and age>21 and sex='m',观察key_len=156,很明显可以知道跟2个条件等值查询一样,也就是说虽然sql语句用了3个条件来查询,但由于用了范围条件age>21导致sex='m'这个条件失效,所以在建立索引时候,要将可能用到范围查询的列放到最后。索引idx_name_age_sex的最后一列是sex,我们在sex列上用范围查询,可以看到key_len=159,跟3个条件等值查询用到的索引字节数一样:

【尽量避免select * 查询】

尽量使用覆盖索引(查询列和索引列一样),减少select  * 操作。当我们把select *改成select name age后,对比一下,发现Extra列多了一个Using index,当出现using index表示系统性能更好,所以,当我们在查询的时候尽可能地保证查询的列能和索引列一样,这样Mysql直接从索引上取值,极大地加大性能:

Mysql的索引优化与explain性能分析_第17张图片

【!=,<>使索引失效】

mysql在使用不等于(!=或者<>)的时候,无法使用索引导致全表扫描。备注:在mysql中,<>相当于!=,即不等于的意思;<=>相当于=,即等于的意思

Mysql的索引优化与explain性能分析_第18张图片

由上图知道,使用了!=时,性能直接由ref级别下降到all级别。但是如果是唯一性索引,例如主键,就算用了!=,索引也可以使用,变为range类型:

Mysql的索引优化与explain性能分析_第19张图片

【is null或is not null使索引失效】

当查询条件是..is  null或者.. is  not  null的时候,由下图可以看出出现两种情况:一种是极端type=null,一种是全表扫描type=all,尽量避免使用!

Mysql的索引优化与explain性能分析_第20张图片

【模糊查询like】

like以通配符开头('%xxx...')mysql索引失效会变成全表扫描的操作。即:'%java'、'%java%'都会失效,但是'java%'就不会失效:

Mysql的索引优化与explain性能分析_第21张图片

可是这样有个问题,条件'java%'和'%java%'查到的数据肯定是不一样的,而且在一般情况下,用'%java%'是最多的,咋保证既要双向模糊查询又不能让索引失效?答案是:覆盖索引,当查询的列与索引列一致,即使用like索引也不会失效:

Mysql的索引优化与explain性能分析_第22张图片

表中建立索引用的列是name、age、sex,还有主键列:id。可以发现,在select查询的列是这四列的任意组合时,type=index,虽不说很高效,但至少比全表扫描ALL好很多,一旦select查询的列是非索引列(如多了addr)可以看到,查询结果type=ALL,继续变成全表扫描。结论:使用模糊查询like,尽量保证查询列与索引列一致,即覆盖索引

【字符串不加引号使索引失效】

当一个varchar类型的数据,查询时不使用引号,会发生隐式类型转换,导致索引失效,进而变成全表扫描,其实就是避免在索引列上加操作

【尽量避免or条件查询】

加了or条件的查询,会让索引失效,变成全表扫描

即使我们在name列上建了索引,即使我们查询条件也用了name,但是因为加了or条件,会让MySQL转向全表扫描

你可能感兴趣的:(mysql)