version : 5.7, from 8.2.1.14 ORDER BY Optimizationhtml
本节描述MySQL什么时候可使用索引来知足ORDER BY子句,当不能使用索引时使用filesort,以及优化器中有关ORDER BY的执行计划信息。mysql
一个order by语句对于有没有使用limit可能存在执行差别。详细内容查看8.2.1.17 LIMIT Query Optimization。算法
在某些状况下,MySQL可能会使用索引来知足一个ORDER BY子句,并避免执行filesort 操做时涉及的额外排序。sql
虽然ORDER BY并不彻底精确地匹配索引,可是索引仍是会被使用,只要在WHERE子句中,全部未被使用的那部分索引(一个索引多个字段-联合索引的状况)以及全部ORDER BY字段都是一个常量就没问题,都会走到索引而不是filesort。bash
这里咱们有一张表tx_order,session
CREATE TABLE `tx_order` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT ,
`serial_number` varchar(255) NOT NULL ,
`order_status` int unsigned DEFAULT 0 NOT NULL ,
`market_id` varchar(10) DEFAULT NULL ,
`market_name` varchar(255) DEFAULT NULL ,
`shop_id` varchar(50) DEFAULT NULL ,
`shop_name` varchar(100) DEFAULT NULL ,
`mobile` varchar(64) DEFAULT NULL ,
`create_date` datetime DEFAULT NULL ,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2333702 DEFAULT CHARSET=utf8;
复制代码
而且添加索引函数
alter table tx_order add index idx_market_date(market_id,create_date);
复制代码
在接下来的sql中分析order by对索引的使用状况。其中MySQL优化器实际执行sql是否使用索引仍是表扫描取决于二者的效率。性能
desc select market_id,create_date from tx_order.tx_order order by market_id,create_date;
1 SIMPLE tx_order index idx_market_date 39 1671956 100 Using index
复制代码
然而这句sql中的查询字段都在索引中,若是查询字段不被包含在索引中,如「select market_id,create_date,market_name」。这种状况下,扫描整个索引而且查找表行以查不在索引中的列,这样的操做的代价可能比表扫描更高,此时优化器可能不会使用索引。优化
desc select market_id,create_date,market_name from tx_order.tx_order order by market_id,create_date;
1 SIMPLE tx_order ALL 1671956 100 Using filesort
复制代码
在InnoDB中,咱们知道主键(汇集索引)自己是索引的一部分,下面这个查询中索引就会被使用。ui
desc select id,market_id,create_date from tx_order.tx_order order by market_id,create_date;
1 SIMPLE tx_order index idx_market_date 39 1671956 100 Using index
复制代码
desc select market_id,create_date from tx_order.tx_order where market_id = '1009' order by create_date;
1 SIMPLE tx_order ref idx_market_date idx_market_date 33 const 170398 100 Using where; Using index
复制代码
desc select market_id,create_date from tx_order.tx_order order by market_id desc ,create_date desc ;
1 SIMPLE tx_order index idx_market_date 39 1671956 100 Using index
desc select market_id,create_date from tx_order.tx_order order by market_id asc ,create_date desc ;
1 SIMPLE tx_order index idx_market_date 39 1671956 100 Using index; Using filesort
复制代码
desc select market_id,create_date from tx_order.tx_order where market_id > '1009' order by market_id asc;
1 SIMPLE tx_order range idx_market_date idx_market_date 33 835978 100 Using where; Using index
desc select market_id,create_date from tx_order.tx_order where market_id < '1009' order by market_id desc;
1 SIMPLE tx_order range idx_market_date idx_market_date 33 230966 100 Using where; Using index
复制代码
desc select market_id,create_date from tx_order.tx_order where market_id = '1009' and create_date>'2018-01-01' order by create_date desc;
1 SIMPLE tx_order range idx_market_date idx_market_date 39 94002 100 Using where; Using index
复制代码
在一些状况下,虽然MySQL对where条件处理的时候用会用到索引,可是不可以用索引来解析order by, 看下面的例子。
desc select market_id,create_date from tx_order.tx_order where market_id='1009' order by market_id ,create_date ;
1 SIMPLE tx_order ref idx_market_id,idx_market_type_create_date idx_market_id 33 const 138084 100 Using where; Using index; Using filesort
复制代码
desc select market_id,create_date from tx_order.tx_order order by market_id asc ,create_date desc;
1 SIMPLE tx_order index idx_market_date 39 1671956 100 Using index; Using filesort
复制代码
desc select mobile from tx_order.tx_order order by abs(mobile);
1 SIMPLE tx_order index idx_mobile 768 1671956 100 Using index; Using filesort
复制代码
desc select a.market_id from tx_order.tx_order a ,tx_order_item b where a.id = b.order_id and a.market_id = '1009' order by a.market_id,b.sku;
1 SIMPLE b ALL idx_order_create 1 100 Using filesort
1 SIMPLE a eq_ref PRIMARY,idx_market_date PRIMARY 8 tx_order.b.order_id 1 10.19 Using where
复制代码
desc select market_id,create_date from tx_order.tx_order group by market_id,create_date order by create_date;
1 SIMPLE tx_order index idx_market_date idx_market_date 39 1671956 100 Using index; Using temporary; Using filesort
复制代码
desc select mobile from tx_order.tx_order order by mobile desc ;
1 SIMPLE tx_order ALL 1671956 100 Using filesort
复制代码
有些状况,使用的表索引的类型不能按顺序保存行。例如,对于HEAP表的HASH索引状况即如此。
排序索引的可用性可能受列别名的使用影响。
在下面的语句中,排序受到影响,不会使用索引.
desc select abs(market_id) as aa from tx_order.tx_order order by aa;
1 SIMPLE tx_order index idx_market_date 39 1671956 100 Using index; Using filesort
复制代码
可是,下面的语句中,虽然查询字段有使用别名,可是真实的排序字段仍是索引中的字段,那么排序仍是使用索引的。
desc select abs(market_id) as aa from tx_order.tx_order order by market_id;
1 SIMPLE tx_order index idx_market_date 39 1671956 100 Using index
复制代码
在默认状况下,对于"group by col2,col2,..."这样的语句,MySQL会同时会包含"order by col2,col2,..."等同于你显示的加速"order by col2,col2,..."排序,这种状况下优化器的处理是没有性能损失的。
对于这个默认状况,若是你想避开默认排序,可使用 order by null 来避免,例如:
desc select market_id,count(market_id) from tx_order.tx_order group by market_id order by null ;
复制代码
优化器可能仍然选择使用排序来实现分组操做。ORDER BY NULL 禁止对结果进行排序,而不是经过分组操做进行先前排序以肯定结果。
注意
GROUP BY默认状况下隐式排序(即,在没有列ASC或 列的DESC指示符的状况下GROUP BY)。可是,不推荐依赖于隐式 GROUP BY排序(即,在没有ASC或 DESC指示符的状况下排序)或显式排序GROUP BY(即,经过 对列使用显式ASC或DESC指示符GROUP BY)。要生成给定的排序顺序,有必要供一个 ORDER BY子句。
当没法使用索引排序的时候,MySQL使用filesort扫描表给结果集排序,相应的filesort在整个查询过程当中产生了额外的排序阶段。
为了支持filesort,优化器实现会分配必定数量的内存sort_buffer_size区域,这块内存区域是每一个session独占的,而且能够更改这个变量值。
若是filesort数据集太大,在内存中没法实现排序,优化器会使用一块磁盘做为临时文件来作排序。某些查询特别适合内存排序完成filesort的操做,例如优化器能够有效的利用内存排序,而不须要临时文件实现。例如
desc select * from tx_order.tx_order order by market_name desc limit 10;
1 SIMPLE tx_order ALL 1671956 100 Using filesort
复制代码
Using temporary的例子
desc select market_name from tx_order.tx_order order by RAND() desc limit 10;
1 SIMPLE tx_order ALL 1671956 100 Using temporary; Using filesort
复制代码
对于filesort的慢查询,能够尝试修改 max_length_for_sort_data 标量来达到效果,控制filesort选择算法的触发点,能够尝试调低 max_length_for_sort_data 值。(若是增大了max_length_for_sort_data的值,而且磁盘使用率上升,cpu使用率降低,)详细资料请阅读 Mysql 排序优化与索引使用(转)。
要提升ORDER BY速度,请检查是否可让MySQL使用索引而不是额外的排序阶段。若是没法作到这一点,请尝试如下策略:
增长 sort_buffer_size 变量值。理想状况下,该值应足够大,以使整个结果集适合排序缓冲区(以免写入磁盘和合并传递),但至少该值必须足够大以容纳15个元组。(最多能够合并15个临时磁盘文件,每一个文件至少有一个元组在内存中必须有空间。)
请考虑存储在排序缓冲区中的列值的大小受 max_sort_length系统变量值的影响。例如,若是元组存储长字符串列的值而且您增长了值 max_sort_length,则排序缓冲区元组的大小也会增长,而且可能须要您增长 sort_buffer_size。对于做为字符串表达式(例如调用字符串值函数的那些)计算的列值,filesort算法没法分辨表达式值的最大长度,所以必须分配 max_sort_length 每一个元组的字节数。
要监视合并传递的数量(合并临时文件),请检查 Sort_merge_passes 状态变量。
增长 read_rnd_buffer_size 变量值,以便一次读取更多行。
将tmpdir 系统变量更改成指向具备大量可用空间的专用文件系统。变量值能够列出以循环方式使用的几个路径; 您可使用此功能将负载分散到多个目录中。:在Unix上用冒号字符()分隔路径,;在Windows上用分号字符()分隔路径。路径应命名位于不一样物理磁盘上的文件系统中的目录 ,而不是同一磁盘上的不一样分区。
使用 EXPLAIN (参见8.8.1 Optimizing Queries with EXPLAIN),能够检查MySQL是否可使用索引来解析ORDER BY子句.
另外,filesort执行的时候优化器的trace能够输出filesort_summary信息快。例如:
"filesort_summary": {
"rows": 100,
"examined_rows": 100,
"number_of_tmp_files": 0,
"sort_buffer_size": 25192,
"sort_mode": "<sort_key, packed_additional_fields>"
}
复制代码
对于MySQL的trace,详细请参考Chapter 8 Tracing the Optimizer.
想要写出高效可靠的排序查询,你须要搞明白order by大概的执行过程,这里能够参考How MySQL executes ORDER BY,Mysql 排序优化与索引使用(转)这两篇文章。
咱们在写sql语句而且使用order by的时候,首先考虑知足索引条件,若是不知足那么知足内存中filesort,最坏的状况就是临时文件出现了,固然这种状况是咱们最不想看到的。
同时这里要说一下个人我的经验:
开放过程当中多去琢磨sql,多看执行计划,有效的避免慢查询,提升服务的性能。