MySQL 查询慢,按限制按顺序分组

Posted

技术标签:

【中文标题】MySQL 查询慢,按限制按顺序分组【英文标题】:Slow MySQL Query, Group By Order By Limit 【发布时间】:2017-03-29 05:35:03 【问题描述】:

我目前加入 5 个表以选择 20 个对象向用户显示,不幸的是,如果我使用 GROUP BYORDER BY 会变得非常慢。

示例查询如下所示:

SELECT r.name, l.name, o.typ, o.id, persons, children, description, rating, totalratings, minprice, picture FROM angebote as a 
JOIN objekte as o ON a.fid_objekt = o.id 
JOIN regionen as r ON a.fid_region = r.id 
JOIN laender as l ON a.fid_land = l.id 
WHERE l.slug="aegypten" AND a.letztes_angebot >= 1 
GROUP BY a.fid_objekt ORDER BY rating DESC LIMIT 0,20 

查询的解释表明:

+------+-------------+-------+--------+----------------------------+------------+---------+---------------------------------------+--------+--------------------------------------------------------+
| id   | select_type | table | type   | possible_keys              | key        | key_len | ref                                   | rows   | Extra                                                  |
+------+-------------+-------+--------+----------------------------+------------+---------+---------------------------------------+--------+--------------------------------------------------------+
|    1 | SIMPLE      | l     | ref    | PRIMARY,slug               | slug       | 767     | const                                 |      1 | Using index condition; Using temporary; Using filesort |
|    1 | SIMPLE      | o     | ALL    | PRIMARY                    | NULL       | NULL    | NULL                                  | 186779 | Using join buffer (flat, BNL join)                     |
|    1 | SIMPLE      | a     | ref    | unique_key,letztes_angebot | unique_key | 8       | ferienhaeuser.o.id,ferienhaeuser.l.id |      1 | Using where                                            |
|    1 | SIMPLE      | r     | eq_ref | PRIMARY                    | PRIMARY    | 4       | ferienhaeuser.a.fid_region            |      1 |                                                        |
+------+-------------+-------+--------+----------------------------+------------+---------+---------------------------------------+--------+--------------------------------------------------------+

所以看起来它没有使用表 objekte 的键,Profiling 说它使用 2.7s 复制到 tmp 表。

我尝试用(SELECT * GROUP BY id) 代替FROM angeboteJOIN objekte,但不幸的是这并没有改善。

用于WHEREORDER BYGROUP BY 的字段也被编入索引。

我想我在这里遗漏了一些基本概念,我们将不胜感激。

由于很可能我在表格上犯了一个错误,这里是它们的描述:

对象

+---------+-------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- --------------------------------------------+ |对象 |创建表`objekte`( `id` int(11) NOT NULL, `typ` varchar(50) NOT NULL, `persons` int(11) NOT NULL, `children` int(11) NOT NULL, `description` 文本不为空, `rating` 浮动 NOT NULL, `totalratings` int(11) NOT NULL, `minprice` 浮动 NOT NULL, `picture` varchar(255) NOT NULL, `last_offer` int(11) NOT NULL, 主键(`id`), KEY `minprice` (`minprice`), KEY `rating` (`rating`), KEY `last_offer` (`last_offer`) ) 引擎=InnoDB 默认字符集=utf8 | +---------+-------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- --------------------------------------------+

安格波特

+------------+-------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- --+ |安格博特 |创建表`angebote`( `id` int(11) NOT NULL AUTO_INCREMENT, `fid_objekt` int(11) 非空, `fid_land` int(11) 非空, `fid_region` int(11) NOT NULL, `fid_subregion` int(11) NOT NULL, `letztes_angebot` int(11) 非空, 主键(`id`), 唯一键`unique_key`(`fid_objekt`,`fid_land`,`fid_region`,`fid_subregion`), KEY `letztes_angebot` (`letztes_angebot`), KEY `fid_objekt` (`fid_objekt`), KEY `fid_land` (`fid_land`), KEY `fid_region` (`fid_region`), KEY `fid_subregion` (`fid_subregion`) ) 引擎=InnoDB AUTO_INCREMENT=2433073 默认字符集=utf8 | +------------+-------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- --+

laender、regionen、subregionen(结构相同)

+---------+-------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -----------------------------------------------+ |兰德|创建表`laender`( `id` int(11) NOT NULL AUTO_INCREMENT, `iso` varchar(2) NOT NULL, `name` varchar(255) NOT NULL, `slug` varchar(255) NOT NULL, `letztes_angebot` int(11) 非空, 主键(`id`), 唯一键 `iso` (`iso`), KEY `slug`(`slug`), KEY `letztes_angebot` (`letztes_angebot`) ) 引擎=InnoDB AUTO_INCREMENT=107 默认字符集=utf8 | +---------+-------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -----------------------------------------------+

【问题讨论】:

我不确定你是否有所有连接的索引,这意味着即使你有索引,分组仍然很慢。 【参考方案1】:

首先这是一个非标准的group by。因此,当您升级到 mysql 5.7 时,它将停止工作。

最大的问题来自objekte 表上没有使用索引。更糟糕的是,您正在对该表的评级字段进行排序,但索引仍未被使用。一种可能的解决方案是创建这样的复合索引:

CREATE INDEX objekte_idx ON objekte(id,rating);

【讨论】:

非常感谢,就是这样,因为我也按其他列排序,我曾经尝试将它们全部索引在一起(id、rating、minprice),但我想我应该单独制作 2 个。我也应该能够通过另一种方式进行分组。 其实mysql每个表只能使用一个索引。因此,制作任何不同的索引都不会导致任何进一步的改进。使用 mysql,多列索引是可行的方法,但您必须正确获取列顺序。 id, rating 恰好在这里使用,但是 rating, id 可能没有!【参考方案2】:

您不需要在这里使用 GROUP BY。您没有使用聚合函数。所以从查询中删除 GROUP BY。删除 Group By 将提高查询性能。也不需要为limit定义0。

SELECT r.name, l.name, o.typ, o.id, persons, children, description, rating, totalratings, minprice, picture FROM angebote as a 
JOIN objekte as o ON a.fid_objekt = o.id 
JOIN regionen as r ON a.fid_region = r.id 
JOIN laender as l ON a.fid_land = l.id 
WHERE l.slug="aegypten" AND a.letztes_angebot >= 1 
ORDER BY rating DESC LIMIT 20 

【讨论】:

我确实使用了 GROUP BY,所以我不会多次获得同一个对象,一个对象可以位于一个地区的不同子区域(或一个国家的区域)。还是我想念为什么我不需要 GROUP BY 呢? 0 也是较大值的占位符。 为此使用 distinct 不幸的是我又犯了一个错误,因为仅删除 GROUP BY 没有帮助。 我们能否看到一些证据表明删除 GROUP BY 会提高性能,或者解释为什么会这样?顺便说一句,虽然我同意 DISTINCT 在没有任何聚合函数的情况下是更正确的方法,但 GROUP BY 通常执行得更快!

以上是关于MySQL 查询慢,按限制按顺序分组的主要内容,如果未能解决你的问题,请参考以下文章

限制按列分组

Mysql查询相关知识(进阶五:分组查询,进阶六 连接查询)

mysql 慢日志怎么按时间查询

MySQL:按查询优化分组

Mysql 的按时间段分组查询

在mysql中按查询排序变得非常慢