MySQL索引优化分析5—排序分组优化

MySQL索引优化分析5—排序分组优化

  • 1.排序中的索引失效
    • 1.1 无过滤 不索引
    • 1.2 顺序错,必排序
    • 1.3 方向反 必排序
    • 1.4 结论
  • 2.索引的选择
    • 结论
  • 3.filesort排序算法
    • 3.1 双路排序
    • 3.2 单路排序
    • 3.3 结论及引申出的问题
    • 3.4 优化策略
  • 4.GROUP BY关键字优化
  • 5.覆盖索引

2.5.5 排序分组优化

1.排序中的索引失效

# 首先创建索引
create index idx_age_deptid_name on emp (age,deptid,name)

MySQL中无法利用索引完成的排序操作称为“文件排序”:

  • 执行计划 Extra字段中若using filesort出现,说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。

以下几种排序、分组情况,是否能使用到索引,能否去掉using filesort?

1.1 无过滤 不索引

无过滤 不索引:当查询语句没有使用过滤条件时,意味着每一条记录都需要查询,此时索引也就用不上了。

也就是说,只有查询语句中有过滤条件用到索引,才可以直接使用索引排序。

explain  select SQL_NO_CACHE * from emp order by age,deptid; 
explain  select SQL_NO_CACHE * from emp order by age,deptid limit 10; 

MySQL索引优化分析5—排序分组优化_第1张图片
查询时间0.062s

MySQL索引优化分析5—排序分组优化_第2张图片

当使用Limit过滤后,文件排序消失,查询效率有所提升,查询时间0.001s。

1.2 顺序错,必排序

当筛选条件与排序条件使用的字段顺序与索引顺序不一致时,Mysql对数据的排序不是按照表内索引顺读取,而是采用外部的索引进行排序。此时,数据的查询性能将会下降。

# 索引顺序:emp (age,deptid,name)
# 可以使用索引
explain select SQL_NO_CACHE * from emp where age=45 order by deptid; 
# 可以使用索引,查询时间:0.028s
explain select SQL_NO_CACHE * from emp where age=45 order by deptid,name; 
# empno字段不在索引中,使用不上索引进行排序,执行时间 0.062s
explain select SQL_NO_CACHE * from emp where age=45 order by deptid,empno;
# 虽筛选条件age在索引列最左,但排序时,字段顺序与索引顺序不一致,使用不上索引进行排序,执行时间 0.089s
explain select SQL_NO_CACHE * from emp where age=45 order by name,deptid; 
# 筛选与排序都不能使用索引:1.01s
explain select SQL_NO_CACHE * from emp where deptid=45 order by age; 

MySQL索引优化分析5—排序分组优化_第3张图片

MySQL索引优化分析5—排序分组优化_第4张图片

总结:当排序没有使用上索引时,就会使用文件排序这种耗费资源的外部索引排序。

1.3 方向反 必排序

当满足前两条条件后,如果排序字段的方向一个为升序、一个为降序,则不会使用自身索引。

explain select * from emp where age=45 order by  deptid desc, name desc ;
explain select * from emp where age=45 order by  deptid asc, name desc ;

MySQL索引优化分析5—排序分组优化_第5张图片

1.4 结论

ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序

2.索引的选择

执行案例前先清除emp上的索引,只留主键:

# 查看索引
SELECT * FROM information_schema.STATISTICS ;
# 调用mydb数据库的存储过程,删除mydb数据库中emp上表的索引
CALL proc_drop_index("mydb","emp");

案例:

查询 年龄为30岁的,且员工编号小于101000的用户,按用户名称排序

1.执行计划:

SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;

在这里插入图片描述
MySQL索引优化分析5—排序分组优化_第6张图片
MySQL索引优化分析5—排序分组优化_第7张图片

2.结果分析:

  • type 是 ALL,即最坏的情况
  • Extra 里还出现了 Using filesort,也是最坏的情况
  • 优化是必须的。

3.开始优化

思路:尽量让where的过滤条件和排序,都使用上索引

但是一共两个字段(age,empno)上有过滤条件,一个字段(name)有排序

1)我们建一个三个字段的组合索引可否?

CREATE INDEX idx_age_empno_name ON emp(age,empno,NAME);

MySQL索引优化分析5—排序分组优化_第8张图片

我们发现using filesort 依然存在,所以name 并没有用到索引。

  • 原因是因为empno是一个范围过滤,所以索引后面的字段不会再使用索引了

所以我们建一个3值索引是没有意义的。

2)为满足排序使用索引的要求,创建两个字段的索引:

empno 和name这个两个字段我只能二选其一,为了优化掉了 using filesort,我们在age,NAME上创建索引。

# 那么我们先删掉这个索引:
DROP INDEX idx_age_empno_name ON emp
# 为了去掉filesort我们可以把索引建成:
CREATE INDEX idx_age_name ON emp(age,NAME);

MySQL索引优化分析5—排序分组优化_第9张图片

MySQL索引优化分析5—排序分组优化_第10张图片
MySQL索引优化分析5—排序分组优化_第11张图片

速度果然提高了4倍。

但是,如果我们选择那个范围过滤,而放弃排序上的索引呢?

3)选择在age,empno上创建索引:

DROP INDEX idx_age_name ON emp;
create index idx_age_eno on emp(age,empno); 

在这里插入图片描述
在这里插入图片描述
果然出现了filesort,而且type还是range光看字面其实并不美好。我们来执行以下sql:

MySQL索引优化分析5—排序分组优化_第12张图片
MySQL索引优化分析5—排序分组优化_第13张图片

结果竟然有 filesort的 sql 运行速度,超过了已经优化掉 filesort的 sql ,而且快了好多倍。何故?

原因是:所有的排序都是在条件过滤之后才执行的,所以如果条件过滤了大部分数据的话,几百几千条数据进行排序其实并不是很消耗性能,即使索引优化了排序但实际提升性能很有限。 相对的 empno<101000 这个条件如果没有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,所以索引放在这个字段上性价比最高,是最优选择。

结论

当范围条件和 group by 或者 order by 的字段出现二选一时优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。

3.filesort排序算法

如果不在索引列上,filesort文件排序有两种算法: mysql就要启动双路排序和单路排序

3.1 双路排序

MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出

从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。

双路排序取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。

3.2 单路排序

从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。

3.3 结论及引申出的问题

由于单路是后出的,总体而言好过双路。但是用单路有问题:

1)在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。

2)本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。

3.4 优化策略

1)增大sort_buffer_size参数的设置

2)增大max_length_for_sort_data参数的设置

3)减少select 后面的查询的字段。

为什么能提高Order By的速度?

1.Order by时select * 是一个大忌只Query需要的字段, 这点非常重要。在这里的影响是:

1)当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。

2)两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。

2.尝试提高 sort_buffer_size

  • 不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的 1M-8M之间调整

3.尝试提高 max_length_for_sort_data

  • 提高这个参数, 会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率.
  • 1024-8192之间调整

4.GROUP BY关键字优化

group by 使用索引的原则几乎跟order by一致 ,唯一区别是:

  • group by 即使没有过滤条件用到索引,也可以直接使用索引。

5.覆盖索引

最后使用索引的手段:覆盖索引

什么是覆盖索引?

  • 简单说就是,select 到 from 之间查询的列 <= 使用的索引列+主键

MySQL索引优化分析5—排序分组优化_第14张图片

USING index:

  • 表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!
  • 如果同时出现using where,表明索引被用来执行索引键值的查找;
  • 如果没有同时出现using where,表明索引只是用来读取数据而非利用索引执行查找。
  • 如:利用索引进行了排序或分组

案例:

1)未使用覆盖索引:

explain select * from emp where name like '%abc';

MySQL索引优化分析5—排序分组优化_第15张图片

MySQL索引优化分析5—排序分组优化_第16张图片

2)使用覆盖索引后

MySQL索引优化分析5—排序分组优化_第17张图片

你可能感兴趣的:(数据库)