mysql order by 优化

在某些情况下,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 禁止对结果进行排序,而不是通过分组操作进行先前排序以确定结果。

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