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语句优化

mysql联合查询

mysql innodb count(distinct)很慢,怎么优化

sql优化