MySQL 查询优化 - distinct、order by 和 limit
Posted
技术标签:
【中文标题】MySQL 查询优化 - distinct、order by 和 limit【英文标题】:MySQL query optimization - distinct, order by and limit 【发布时间】:2010-05-27 03:37:44 【问题描述】:我正在尝试优化以下查询:
select distinct this_.id as y0_
from Rental this_
left outer join RentalRequest rentalrequ1_
on this_.id=rentalrequ1_.rental_id
left outer join RentalSegment rentalsegm2_
on rentalrequ1_.id=rentalsegm2_.rentalRequest_id
where
this_.DTYPE='B'
and this_.id<=1848978
and this_.billingStatus=1
and rentalsegm2_.endDate between 1273631699529 and 1274927699529
order by rentalsegm2_.id asc
limit 0, 100;
此查询连续多次执行,用于记录的分页处理(每次都有不同的限制)。它返回我在处理过程中需要的 id。我的问题是这个查询需要超过 3 秒。我在三个表中的每一个中都有大约 200 万行。
解释给出:
+----+-------------+--------------+--------+-----------------------------------------------------+---------------+---------+--------------------------------------------+--------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+--------+-----------------------------------------------------+---------------+---------+--------------------------------------------+--------+----------------------------------------------+
| 1 | SIMPLE | rentalsegm2_ | range | index_endDate,fk_rentalRequest_id_BikeRentalSegment | index_endDate | 9 | NULL | 449904 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | rentalrequ1_ | eq_ref | PRIMARY,fk_rental_id_BikeRentalRequest | PRIMARY | 8 | solscsm_main.rentalsegm2_.rentalRequest_id | 1 | Using where |
| 1 | SIMPLE | this_ | eq_ref | PRIMARY,index_billingStatus | PRIMARY | 8 | solscsm_main.rentalrequ1_.rental_id | 1 | Using where |
+----+-------------+--------------+--------+-----------------------------------------------------+---------------+---------+--------------------------------------------+--------+----------------------------------------------+
我尝试删除 distinct 并且查询运行速度快了三倍。不带查询的解释给出:
+----+-------------+--------------+--------+-----------------------------------------------------+---------------+---------+--------------------------------------------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+--------+-----------------------------------------------------+---------------+---------+--------------------------------------------+--------+-----------------------------+
| 1 | SIMPLE | rentalsegm2_ | range | index_endDate,fk_rentalRequest_id_BikeRentalSegment | index_endDate | 9 | NULL | 451972 | Using where; Using filesort |
| 1 | SIMPLE | rentalrequ1_ | eq_ref | PRIMARY,fk_rental_id_BikeRentalRequest | PRIMARY | 8 | solscsm_main.rentalsegm2_.rentalRequest_id | 1 | Using where |
| 1 | SIMPLE | this_ | eq_ref | PRIMARY,index_billingStatus | PRIMARY | 8 | solscsm_main.rentalrequ1_.rental_id | 1 | Using where |
+----+-------------+--------------+--------+-----------------------------------------------------+---------------+---------+--------------------------------------------+--------+-----------------------------+
如您所见,Using temporary
是在使用 distinct 时添加的。
我已经为 where 子句中使用的所有字段建立了索引。 我可以做些什么来优化这个查询吗?
非常感谢!
编辑:我尝试按照建议在 this_.id 上订购,但查询速度慢了 5 倍。这是解释计划:
+----+-------------+--------------+------+-----------------------------------------------------+---------------------------------------+---------+------------------------------+--------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+------+-----------------------------------------------------+---------------------------------------+---------+------------------------------+--------+----------------------------------------------+
| 1 | SIMPLE | this_ | ref | PRIMARY,index_billingStatus | index_billingStatus | 5 | const | 782348 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | rentalrequ1_ | ref | PRIMARY,fk_rental_id_BikeRentalRequest | fk_rental_id_BikeRentalRequest | 9 | solscsm_main.this_.id | 1 | Using where; Using index; Distinct |
| 1 | SIMPLE | rentalsegm2_ | ref | index_endDate,fk_rentalRequest_id_BikeRentalSegment | fk_rentalRequest_id_BikeRentalSegment | 8 | solscsm_main.rentalrequ1_.id | 1 | Using where; Distinct |
+----+-------------+--------------+------+-----------------------------------------------------+---------------------------------------+---------+------------------------------+--------+----------------------------------------------+
【问题讨论】:
【参考方案1】:-
从执行计划中,我们看到优化器足够聪明,可以理解您在这里不需要 OUTER JOIN。无论如何,您最好明确指定。
DISTINCT
修饰符表示您要对 SELECT 部分中的所有字段进行 GROUP BY,即 ORDER BY 所有指定字段,然后丢弃重复项。换句话说,order by rentalsegm2_.id asc
子句在这里没有任何意义。
下面的查询应该返回等效的结果:
select distinct this_.id as y0_
from Rental this_
join RentalRequest rentalrequ1_
on this_.id=rentalrequ1_.rental_id
join RentalSegment rentalsegm2_
on rentalrequ1_.id=rentalsegm2_.rentalRequest_id
where
this_.DTYPE='B'
and this_.id<=1848978
and this_.billingStatus=1
and rentalsegm2_.endDate between 1273631699529 and 1274927699529
limit 0, 100;
UPD
如果您希望执行计划以RentalSegment
开头,则需要将以下索引添加到数据库中:
-
RentalSegment(结束日期)
RentalRequest(id,rental_id)
租借(id、DTYPE、billingStatus)或(id、billingStatus、DTYPE)
查询可以改写如下:
SELECT this_.id as y0_
FROM RentalSegment rs
JOIN RentalRequest rr
JOIN Rental this_
WHERE rs.endDate between 1273631699529 and 1274927699529
AND rs.rentalRequest_id = rr.id
AND rr.rental_id <= 1848978
AND rr.rental_id = this_.id
AND this_.DTYPE='D'
AND this_.billingStatus = 1
GROUP BY this_.id
LIMIT 0, 100;
如果执行计划不是从 RentalSegment 开始,您可以使用 STRAIGHT_JOIN
强制执行。
【讨论】:
我不明白为什么 order by 没有意义。由于这是一个分页标准(我将在每次调用中增加第一个参数限制),我必须指定一个 oder,否则结果将不会总是以相同的顺序返回,并且分页将不起作用。我错过了什么吗? @Manuel Darveau:字段rentalsegm2_.id
未在SELECT 部分中提及,并且可能与this._id 存在一对多关系。当您 GROUP BY this._id
时,按 rentalsegm2_.id
排序不再有意义,因为执行排序的项目可以任意取。 GROUPing 操作(和 DISTINCT)假定按分组列进行隐式排序(您甚至可以在 mysql 中说 GROUP BY this_id DESC
),结果按排序顺序排列。
@Manuel Darveau:我的意思是rentalsegm2_.id
的排序没有意义,但this_.id asc
的显式排序可以(在这种情况下是隐含的)。
我以前按 this_.id 订购,但查询时间延长了 5 倍。问题是mysql首先在Rental表上选择,然后在RentalRequest和RentalSegment上。解释计划与我最初发布的相反。我测试了你的查询并用了 12 秒(与我原来的查询用了 3 秒相比)。我最大的限制是在 RentalSegment 上,所以我猜 MySQL 应该启动它的查询。【参考方案2】:
没有 distinct 的查询运行得更快的原因是你有一个限制子句。如果没有 distinct,服务器只需要查看前一百个匹配项。然而,其中一些行可能有重复的字段,因此如果您引入 distinct 子句,服务器必须查看更多行才能找到没有重复值的行。
顺便说一句,你为什么使用 OUTER JOIN?
【讨论】:
@Manuel Darveau:确实,为什么要使用 OUTER JOIN。您是否使用“正常”连接尝试/验证了结果。应该更快!【参考方案3】:对于“rentalsegm2_”表,优化器选择了“index_endDate”索引,该表的预期行数约为 45 万。由于存在其他 where 条件,您可以检查“this_”表索引。我的意思是您可以在“this_ table”中查看每个 where 条件影响了多少记录。
总之,您可以通过更改优化器使用的索引来尝试替代解决方案。 这可以通过“USE INDEX”、“FORCE INDEX”命令获得。
谢谢
Rinson KE 数据库管理员 www.qburst.com
【讨论】:
以上是关于MySQL 查询优化 - distinct、order by 和 limit的主要内容,如果未能解决你的问题,请参考以下文章
非常慢的 MySQL COUNT DISTINCT 查询,即使有索引——如何优化?
mysql中去重 用group by优化distinct 用法
mysql GROUP BY、DISTINCT、ORDER BY语句优化