在某些情况下,MySQL可能会使用索引来满足一个ORDER BY子句,并避免执行filesort 操作时涉及的额外排序。
虽然ORDER BY并不完全精确地匹配索引,但是索引还是会被使用,只要在WHERE子句中,所有未被使用的那部分索引(一个索引多个字段-联合索引的情况)以及所有ORDER BY字段都是一个常量就没问题,都会走到索引而不是filesort。
这里我们有一张表tx_order:
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是否使用索引还是表扫描取决于两者的效率。
下面这个sql中,优化器使用了idx_market_date索引避开了表扫描.
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中,我们知道主键(聚集索引)本身是索引的一部分,下面这个查询中索引就会被使用。
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
下面这种情况,在where条件中索引中的一个字段是一个常量,并且where子语句产生的范围索引的性能比表扫描高的多,那么这样的查询会选择索引而不是表扫描。
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
下面两条sql比较特殊,也可以对比前面几个order by … asc的语句。看看下面的执行结果我们可以思考这是为什么。首先添加索引的时候暂时是无法指定字段排序的,alter table tx_order add index idx_market_date(market_id asc,create_date desc),虽然这样的写法语法是支持的,但是当前版本的MySQL不做任逻辑何支持,都是统一按照默认升序排列。在一个联合索引中,查询按照索引中的字段排序,如果排序方式不一致,优化器还是会部分走表扫描的。
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
下面的查询where子语句中的范围索引优于表扫描,优化器会选择索引解析order by。
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
下面这个查询中,order by的不再是market_id,但是所有查询的行market_id都是一个常量,所以还是会走到索引的解析order by。
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, 看下面的例子。
order by使用到的索引非连续,MySQL解析order by还是会扫描表,我这里有一个索引 idx_market_id(market_id,order_status,create_date),看下面的sql执行结果。
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
混合排序asc,desc
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
order by字段使用函数,优化器解析order by放弃索引
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
有不同的ORDER BY和GROUP BY表达式。
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
在下面的语句中,排序受到影响,不会使用索引.因为aa在索引上找不到.
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 禁止对结果进行排序,而不是通过分组操作进行先前排序以确定结果。