mysql调优--根据explain结果分析索引有效性,正确使用索引

定位低效率sql

对于mysql性能优化,除了宏观层面的网络、设备等优化,sql语句的优化是极为重要的一环,需要想办法找到对应的执行效率低的sql语句进行优化。

慢查询日志

慢查询日志是定位低效率sql的手段之一,通过以下命令,设置开启慢查询日志。

#显示是否开启了慢查询日志
show variables like 'slow_query_log'; 
#开启慢查询日志
set global slow_query_log = on 
#慢查询日志输出位置
set global slow_query_log_file = '/var/lib/mysql/gupaoedu-slow.log' 
set global log_queries_not_using_indexes = on 
#设置耗时查询时间阈值
set global long_query_time = 0.1 

有了慢查询日志之后,可以通过mysqldumpslow分析哪一条sql语句需要进行优化,建立合适的索引。

Druid数据源的监控

使用阿里的Druid数据源时可以对接自带的监控平台,非常方便有各种统计数据,可以监控慢sql

业务定位

这个就非常粗暴了,只管堆数据,然后看接口的效率,可以粗略定位sql语句

explain输出格式含义分析

explain输出格式含义,这一个在mysql官网有完整的解释,奈何英语水平有限,看的难受,幸好发现这位博主的一篇文章,解释的非常全面,易懂,强烈推荐EXPLAIN用法和结果分析,了解的explain的输出含义,才能做以下分析。

深入了解explain结果分析,了解每种情况会出现什么样的explain结果输出,以此能够更加清晰明了、正确地使用索引。

根据explain分析索引有效性

假设有如下表结构,创建主键为id,联合索引为(biz_no,cus_code),单列唯一索引uni_no

-- auto-generated definition
create table index_test
(
  id       int auto_increment
    primary key,
  biz_no   varchar(11) default '' not null,
  cus_code varchar(11) default '' not null,
  uni_no   varchar(11) default '' not null,
  constraint idx_biz_no_cus_code
    unique (biz_no, cus_code),
  constraint uk_uni_no
    unique (uni_no)
);

执行以下存储过程,插入数据。

#定义存储过程
delimiter //
DROP PROCEDURE IF EXISTS insert_test_val;
##num_limit 要插入数据的数量,begin_num 起始数字
CREATE PROCEDURE insert_test_val(in num_limit int,in begin_num int)
BEGIN
  DECLARE i int default 1;
  DECLARE a varchar(11) default '';
  DECLARE b varchar(11) default '';
  DECLARE c varchar(11) default '';
  WHILE i<=num_limit do
  set a = concat('bizno',begin_num);
  set b = concat('cus',begin_num);
  set c =concat('uni',begin_num);
  INSERT into index_test(biz_no, cus_code, uni_no) values (a,b,c);
  set i = i + 1;
  set begin_num = begin_num + 1;
  END WHILE;
END


#调用存储过程
call insert_test_val(200000,1);

单列索引

在where条件中不同的使用方式可能会对索引的有效性有不同的影响,这里简单分析几种情况。

情况1:key=?情况

对于单列索引,最常见的是key=?的情况,例如针对上面的表结构,执行以下sql,分析explain结果。

explain select * from index_test where uni_no='uni500';

执行结果如下
在这里插入图片描述

可以看出,type是const,表示是一次索引就能得到数据,索引有效。

如果我们了解innodb索引树的结构,应该可以知道,这里的索引过程应该是,先从辅助索引树uni_no的索引树检索得到主键id,然后还需要从主键索引树检索目标记录,因此除了能够使用索引之外,还可以进一步优化,就是根据业务需求,看能否使用上覆盖索引

覆盖索引

例如以下sql。只选择id的话,在辅助索引树就能完成,不需要再去主键索引树检索,大大提高了性能。

explain select id from index_test where uni_no='uni500';


在这里插入图片描述

情况2:key > ? | key < ?情况。

对于范围查询,会影响查询效率,这个是必然的,但是在范围条件中建立索引,能否够提升查询效率?

索引生效

执行如下sql,使用索引进行范围查询

select * from index_test where uni_no < 'uni10000';

结果是下图,只有四条记录。


mysql调优--根据explain结果分析索引有效性,正确使用索引_第1张图片
执行explain sql分析执行计划,结果如下图,可以看出,这里的type使用的是range类型,也就是从指定的范围开始检索记录,uni_no索引生效。


在这里插入图片描述

反转,索引不生效

但是假如我们执行以下sql,把条件改为大于号,得到的explain输出确实不一样。

explain select * from index_test where uni_no > 'uni10000';

输出,此时执行的是全表扫描,索引不生效,这是为什么?


在这里插入图片描述

思考,为何范围查询索引时而生效时而不生效

我在数据库表中插入总共20万数据,其中条件为uni_no < 'uni10000'的数据只有四条。而反过来的数据则有199996条,约等于全表数据。我们可以知道在不是覆盖索引的情况下,使用辅助索引树检索数据需要检索两遍,假如mysql在uni_no索引树范围内逐条检索到199996条主键记录符合,然后每条主键记录都去主键索引树检索记录数据的话,效率还不如全表扫描。

因此执行计划对此有相应优化,如果查询优化器预计辅助索引树检索到的符合的记录量特别大的情况下,就不会使用辅助索引树查询,反而使用全表扫描来完成检索,例如uni_no > 'uni10000'的情况。反之,如果预计辅助索引树检索到的记录不多的情况下,还是会使用辅助索引来检索,例如uni_no < 'uni10000'的情况.

但是需要注意,非唯一索引树精确值等值检索,不管匹配多少记录,都是走索引树,不会因为匹配多了就不走索引树检索,猜测是因为非唯一索引树精确检索效率很高,type属于ref类型,因此先检索辅助索引树在检索主键索引树的效率还是比all全表要高。

再次思考,在单列覆盖索引的情况下,是否应该一直生效

根据上面的思考再次推论,在覆盖索引的情况下,检索数据不需要在辅助索引树和主键索引树分别检索两遍,只需要在辅助索引树检索就能得到对应数据,那么直接在辅助索引树上进行查询应该是最优的策略,推测不管是uni_no < 'uni10000'还是uni_no > 'uni10000',索引都应该生效。

执行以下sql验证。

explain select id from index_test where uni_no > 'uni10000';
explain select id from index_test where uni_no < 'uni10000';

结果查询的type都是range,索引生效,推测正确。


在这里插入图片描述

在这里插入图片描述

类比,其他索引时而生效时而不生效的情况

在key > ? | key < ?情况下,索引是否生效并不是绝对的。因为是范围查询,导致辅助索引树可能匹配到多条记录,然后到逐渐索引树检索,这种情况索引就不会生效,反而会使用all查询。其实类似的情形可以类比到一些辅助索引树匹配到记录比较多的情况。

order by或者group by索引时而生效时而不生效

例如本例子中,对比以下两个sql的输出,你会发现也是索引不一定生效的,type不一定是range,一切有查询优化器决定。

explain select * from index_test where biz_no > 'bizno10000' order by biz_no,cus_code;
#辅助索引匹配记录预计趋近全表,不使用索引
#输出,使用全表扫描,filesort外部排序
1	SIMPLE	index_test	ALL	idx_biz_no_cus_code				199586	Using where; Using filesort

#但是假如是覆盖索引,辅助树匹配记录预计趋近全表,则还是会使用索引排序,如下sql
explain select biz_no from index_test where biz_no > 'bizno10000' order by biz_no,cus_code;
#输出,使用range,索引排序
1	SIMPLE	index_test	range	idx_biz_no_cus_code	idx_biz_no_cus_code	13		99793	Using where; Using index

explain select * from index_test where biz_no < 'bizno10000' order by biz_no,cus_code;
#辅助索引匹配记录预计较少,
#输出,使用range,索引排序
1	SIMPLE	index_test	range	idx_biz_no_cus_code	idx_biz_no_cus_code	13		4	Using where

order by或者group byPS分析总结:当order by出现filesort时考虑以下问题

  1. order by条件是否已经建立索引?多个order by列时,还需要建立联合索引来提升,避免出现filrsort。
  2. 如果确认order by已经建立索引,那么再看看是否加了where条件和where条件是否走order by列的索引?如果不是就会出现filesort。如果where条件与order by使用的是同一个索引,还是出现filesort,继续往下看
  3. 如果where条件与order by使用的是同一个索引,还是出现filesort,考虑是不是where条件在辅助索引树匹配的记录太多,导致查询优化器直接不走索引树查询,如果是,请考虑控制where条件或者考虑配合业务手段,使用覆盖索引方式操作来达到走索引的目的。

当然,出现filesort并不是说就一定是大问题,如果select的explain 输出type是range以上,使用filesort也是可以接受的,只是如果对性能有很高要求时,可以考虑这样子去优化。

##这是一个查询类型是range,但是使用filesort的sql,效率也不差
explain select * from index_test where uni_no < 'uni10000'  order by biz_no,cus_code;
#输出
1	SIMPLE	index_test	range	uk_uni_no	uk_uni_no	13		4	Using where; Using filesort

情况3:<> |!=

这些情况下不走索引,explain 输出type都是all

总结

单列索引有效性分析总结以下几点:

  1. 精确匹配索引生效

  2. 范围查找情况,索引生效判断依据以下规则:

    • 覆盖索引下,范围查找索引一律生效

    • 非覆盖索引查询,如果预计辅助索引树检索范围内匹配记录数据量较大,则索引不生效,做All扫描;

      如果预计索引树检索范围内匹配记录数据量较小,则索引生效,做range类型查询。

  3. order by、group by等没有使用索引排序出现filesort,参考PS分析总结

  4. like的情况按照最左匹配原则生效

  5. 没使用索引精确或者范围查询,索引不可能生效。非等查询,索引也不能生效。

联合索引

分析联合索引的有效性时,首先需要知道的点是联合索引的匹配原则是最左匹配。

情况1:key1=? and key2=?

假如有联合索引(key1,key2),那么在完全精确匹配的情况下,索引是有效的,查询类型type是const,表示一次索引就能得到结果。例如以下例子。

explain select * from index_test where biz_no='bizno5000' and cus_code='cus5000';

输出如图所示


在这里插入图片描述

类似地,两列以上的联合索引精确匹配也是const类型查询。

情况二:key1=?

假如有联合索引(key1,key2),在没有精确匹配的情况下只使用key1=?作为索引条件,遵循最左匹配原则,只用最部分最左条件进行索引查询,索引生效情况如何?以下例子。

explain select * from index_test where biz_no='bizno5000';

结果输出如图下所示,可以看出查询类型下降一个级别,变成ref,索引可以匹配多条记录,但是效率依然很高,索引依然生效。


在这里插入图片描述

类似地,两列以上的联合索引,依据最左前缀原则匹配查询,查询type也是ref。例如联合索引(key1,key2,key3),使用where条件是key1=‘a’ and key2=‘b’,查询type也是ref。

精确查询与范围查询结合

类似地,假如最左列使用精确检索,右边列使用范围检索,精确查询效果如何?

explain select * from index_test where biz_no = 'bizno10000' and cus_code >= 'cus10000';

输出查询类型为ref,索引生效.

情况3:联合索引仅范围查询

跟单列索引一样,涉及到仅使用范围查询分析索引生效情况,必须分为是否是覆盖索引的查询来分析。要想仅使用范围查询,并且联合索引生效,还是需要按照最左前缀匹配,所以只有在最左列作为范围条件时才可能生效。因此最终的分析方式其实跟单列的索引范围查询一样分析。

非覆盖索引

辅助索引树匹配范围记录较少的情况?

使用最左列进行范围查询, biz_no < 'bizno10000’记录较少,只有四条。

explain select * from index_test where biz_no < 'bizno10000' ;

输出结果如下图所示,虽然使用了范围查询,但是辅助索引树预计范围内匹配记录数量较少,那么再次扫描主键索引树获取全部数据的消耗时间不会很高,因此还是使用查询类型type是range的查询。此时索引生效。


在这里插入图片描述

辅助索引树匹配范围记录较多的情况?

使用最左列进行范围查询, biz_no > 'bizno10000’记录趋近全表。

explain select * from index_test where biz_no > 'bizno10000' ;

输出结果如下图所示,联合索引的索引树预计范围内匹配的数据量比较大,再次扫描主键索引树获取全部数据的消耗时间可能较高,所以查询优化器优化决定使用全表扫描代替range查询。此时索引不生效。


在这里插入图片描述

覆盖索引

覆盖索引下,减少了再次扫描主键索引树的过程,那么其实不管联合索引的索引树匹配多少条记录都是无所谓的,因此都是使用range查询,索引都会生效。

例如匹配记录多的情况。

explain select biz_no from index_test where biz_no > 'bizno10000' ;

输出如下图所示,虽然预计扫描行数较多,但是还是使用range查询,索引生效,因为不需要再次扫描主键索引树,range查询联合索引树效率最高。Extra中的Using Index使用覆盖索引查询。


在这里插入图片描述

不用说,匹配记录少的情况同理也是一样。

总结

联合索引的索引生效原则如下:

  1. 所有列精确匹配索引一定生效
  2. 最左列精确匹配,右边列使用范围查询、非等查询、不使用右边列,索引一样生效。
  3. 最左列使用范围查询时:
    • 如果是覆盖查询,索引一律生效
    • 如果不是覆盖查询,当查询优化器预计范围匹配记录较大的时候,索引不生效;当查询优化器预计范围匹配记录较小的时候,索引生效。
  4. 如果最左列没使用精确或者范围查询,联合索引不可能生效。
  5. like 查询跟单列索引类似,也是最左匹配原则。

你可能感兴趣的:(mysql)