2.5.5 排序分组优化
# 首先创建索引
create index idx_age_deptid_name on emp (age,deptid,name)
MySQL中无法利用索引完成的排序操作称为“文件排序”:
以下几种排序、分组情况,是否能使用到索引,能否去掉using filesort?
无过滤 不索引:当查询语句没有使用过滤条件时,意味着每一条记录都需要查询,此时索引也就用不上了。
也就是说,只有查询语句中有过滤条件用到索引,才可以直接使用索引排序。
explain select SQL_NO_CACHE * from emp order by age,deptid;
explain select SQL_NO_CACHE * from emp order by age,deptid limit 10;
当使用Limit过滤后,文件排序消失,查询效率有所提升,查询时间0.001s。
当筛选条件与排序条件使用的字段顺序与索引顺序不一致时,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;
总结:当排序没有使用上索引时,就会使用文件排序这种耗费资源的外部索引排序。
当满足前两条条件后,如果排序字段的方向一个为升序、一个为降序,则不会使用自身索引。
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 ;
ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
执行案例前先清除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 ;
2.结果分析:
3.开始优化
思路:尽量让where的过滤条件和排序,都使用上索引
但是一共两个字段(age,empno)上有过滤条件,一个字段(name)有排序
1)我们建一个三个字段的组合索引可否?
CREATE INDEX idx_age_empno_name ON emp(age,empno,NAME);
我们发现using filesort 依然存在,所以name 并没有用到索引。
所以我们建一个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);
速度果然提高了4倍。
…
但是,如果我们选择那个范围过滤,而放弃排序上的索引呢?
3)选择在age,empno上创建索引:
DROP INDEX idx_age_name ON emp;
create index idx_age_eno on emp(age,empno);
果然出现了filesort,而且type还是range光看字面其实并不美好。我们来执行以下sql:
结果竟然有 filesort的 sql 运行速度,超过了已经优化掉 filesort的 sql ,而且快了好多倍。何故?
原因是:所有的排序都是在条件过滤之后才执行的,所以如果条件过滤了大部分数据的话,几百几千条数据进行排序其实并不是很消耗性能,即使索引优化了排序但实际提升性能很有限。 相对的 empno<101000 这个条件如果没有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,所以索引放在这个字段上性价比最高,是最优选择。
当范围条件和 group by 或者 order by 的字段出现二选一时 ,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。
如果不在索引列上,filesort文件排序有两种算法: mysql就要启动双路排序和单路排序
MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出
从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。
双路排序取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。
从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。
由于单路是后出的,总体而言好过双路。但是用单路有问题:
1)在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。
2)本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
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
3.尝试提高 max_length_for_sort_data
group by 使用索引的原则几乎跟order by一致 ,唯一区别是:
最后使用索引的手段:覆盖索引
什么是覆盖索引?
USING index:
案例:
1)未使用覆盖索引:
explain select * from emp where name like '%abc';
2)使用覆盖索引后