【问题标题】:GROUP BY + ORDER BY make my query very slowGROUP BY + ORDER BY 让我的查询很慢
【发布时间】:2017-07-13 00:45:59
【问题描述】:

我试图弄清楚我应该对我的查询和/或我的表结构做些什么来改进查询以获得在 1 秒内运行的畅销书。

这是我正在谈论的查询:

SELECT pr.id_prod, MAX(pr.stock) AS stock, MAX(pr.dt_add) AS dt_add, SUM(od.quantity) AS quantity
    FROM orders AS o
    INNER JOIN orders_details AS od ON od.id_order = o.id_order
    INNER JOIN products_references AS pr ON pr.id_prod_ref = od.id_prod_ref
    INNER JOIN products AS p ON p.id_prod = pr.id_prod
    WHERE o.id_order_status > 11
    AND pr.active = 1
    GROUP BY p.id_prod
    ORDER BY quantity
    LIMIT 10

如果我使用GROUP BY p.id_prod 而不是GROUP BY pr.id_prod 并删除ORDER BY,则查询将在0.07 秒内运行。

EXPLAIN 表可以吗?

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  o   range   PRIMARY,id_order_status id_order_status 1       75940   Using where; Using index; Using temporary; Using filesort
1   SIMPLE  od  ref id_order,id_prod_ref    id_order    4   dbname.o.id_order   1   
1   SIMPLE  pr  eq_ref  PRIMARY,id_prod PRIMARY 4   dbname.od.id_prod_ref   1   Using where
1   SIMPLE  p   eq_ref  PRIMARY,name_url,id_brand,name  PRIMARY 4   dbname.pr.id_prod   1   Using index

这是没有 ORDER BY 的解释

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  p   index   PRIMARY,name_url,id_brand,name  PRIMARY 4       1   Using index
1   SIMPLE  pr  ref PRIMARY,id_prod id_prod 4   dbname.p.id_prod    2   Using where
1   SIMPLE  od  ref id_order,id_prod_ref    id_prod_ref 4   dbname.pr.id_prod_ref   67  
1   SIMPLE  o   eq_ref  PRIMARY,id_order_status PRIMARY 4   dbname.od.id_order  1   Using where

这是表结构

CREATE TABLE `orders` (
 `id_order` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `id_dir` int(10) unsigned DEFAULT NULL,
 `id_status` tinyint(3) unsigned NOT NULL DEFAULT '11',
 PRIMARY KEY (`id_order`),
 KEY `id_dir` (`id_dir`),
 KEY `id_status` (`id_status`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


CREATE TABLE `orders_details` (
 `id_order_det` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `id_order` int(10) unsigned NOT NULL,
 `id_prod_ref` int(10) unsigned NOT NULL,
 `quantity` smallint(5) unsigned NOT NULL DEFAULT '1',
 PRIMARY KEY (`id_order_det`),
 UNIQUE KEY `id_order` (`id_order`,`id_prod_ref`) USING BTREE,
 KEY `id_prod_ref` (`id_prod_ref`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


CREATE TABLE `products` (
 `id_prod` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `name` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
 PRIMARY KEY (`id_prod`),
 FULLTEXT KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


CREATE TABLE `products_references` (
 `id_prod_ref` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `id_prod` int(10) unsigned NOT NULL,
 `stock` smallint(6) NOT NULL DEFAULT '0',
 `dt_add` datetime DEFAULT NULL,
 `active` tinyint(1) NOT NULL DEFAULT 0,
 PRIMARY KEY (`id_prod_ref`),
 KEY `id_prod` (`id_prod`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

我还尝试为您提供表关系(ON UPDATE,ON DELETE CASCADE,...),但没有设法将其导出。但我认为这目前并不重要!

【问题讨论】:

  • 为什么按不在 SELECT 中的列分组? GROUP BY p.id_prod 是正确的方法
  • 也感谢 Mihai 的快速回复 :) 好吧,即使我在 SELECT 中使用 pr.id_prod,它也不会改变任何东西:/
  • 好的,在你的查询前加上一个解释,然后用结果编辑你的问题
  • 正如我在最初的帖子中所说,我试图把 EXPLAIN 放在一边,但它完全是一团糟。有没有更好的复制粘贴方法?
  • 从您的 mysql 客户端将其复制为表或类似选项。

标签: mysql group-by sql-order-by query-optimization


【解决方案1】:

尝试按顺序使用别名,而不是表中的值

并将 group by 用于 select 中的值(对于 join 是相同的,因为是相等值的内部连接,并且不会为 select 结果检索 pr 的值)

 SELECT p.id_prod, p.name, SUM(od.quantity) AS quantity
 FROM orders AS o
 INNER JOIN orders_details AS od ON od.id_order = o.id_order
 INNER JOIN products_references AS pr ON pr.id_prod_ref = od.id_prod_ref
 INNER JOIN products AS p ON p.id_prod = pr.id_prod
 WHERE pr.active = 1
 GROUP BY p.id_prod
 ORDER BY quantity
 LIMIT 10  

不要忘记在连接列上使用适当的索引

【讨论】:

  • 感谢 scaisEdge 的快速回复 :) 对不起,我已经在使用别名了。我更正了我最初的帖子。
  • 好的,但是使用 p.id_prod 你应该提高你的性能,因为你不需要检索 pr.id_prod .. values .. 。最后你需要在你的连接列上有适当的索引..
  • 确实,在 GROUP BY 中使用 p.id_prod 在没有 ORDER BY 的情况下可以显着提高性能,但是抱歉,我还是不明白为什么! JOIN 中使用的所有列都被索引。我使用 innoDB 并设置索引列之间的所有事务
  • 很简单 .. 如果您使用 p.id_prod .. 对用于其余查询的相同“信息”的查询访问 .. 如果您使用 pr.id_prod 组,这些值必须由查询引擎额外检索,以构建 order by ...所需的信息,因此使用 pr.di_prod 您可以向数据请求更多数据,并且查询引擎可以工作更多..
  • 好的,但是如果我使用 SELECT pr.id_prod 然后 GROUP BY pr.id_prod 怎么办?可以吗?
【解决方案2】:

(在OP添加更多信息后重写。)

SELECT  pr.id_prod,
        MAX(pr.stock) AS max_stock,
        MAX(pr.dt_add) AS max_dt_add
        SUM(od.quantity) AS sum_quantity
    FROM  orders AS o
    INNER JOIN  orders_details AS od
            ON od.id_order = o.id_order
    INNER JOIN  products_references AS pr
            ON pr.id_prod_ref = od.id_prod_ref
    WHERE  o.id_order_status > 11
      AND  pr.active = 1
    GROUP BY  pr.id_prod
    ORDER BY  sum_quantity
    LIMIT  10

请注意,p 已被删除,因为它无关紧要。

在将JOINGROUP BY 一起使用时,请注意SUM() -- 您可能会得到一个错误的、夸大的值。

一张桌子的改进:

CREATE TABLE `orders_details` (
 `id_order` int(10) unsigned NOT NULL,
 `id_prod_ref` int(10) unsigned NOT NULL,
 `quantity` smallint(5) unsigned NOT NULL DEFAULT '1',
 PRIMARY KEY (`id_order`,`id_prod_ref`),
 INDEX (id_prod_ref, id_order)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

原因如下:od 听起来像是一个多:多映射表。请参阅here 以获取有关提高其中性能的提示。

GROUP BY通常涉及排序。 ORDER BY,当它与GROUP BY不同时,肯定需要另一种排序。

删除ORDER BY 允许查询返回任意10 行而不进行排序。 (这可以解释时间上的差异。)

注意别名sum_quantity 以避免 quantity 和您的别名 quantity 之间的歧义。

解释解释

1   SIMPLE  o   range   id_order_status 1                75940   Using where; Using index; Using temporary; Using filesort
1   SIMPLE  od  ref     id_order        4   o.id_order       1   
1   SIMPLE  pr  eq_ref  PRIMARY         4   od.id_prod_ref   1   Using where
1   SIMPLE  p   eq_ref  PRIMARY         4   pr.id_prod       1   Using index
  • 将按照给定的顺序 (o,od,pr,p) 访问这些表。
  • o 不会使用数据(“使用索引”),但会扫描包含(id_status, id_order)id_order_status 索引。注意:PRIMARY KEY隐式添加到任何辅助键。
  • 估计需要扫描 76K(对于 > 11)。
  • 在处理的某个地方,会有一个临时表和它的一个排序。这可能也可能不涉及磁盘 I/O。
  • 到达od 可能会找到 1 行,可能会找到 0 或超过 1 个(“ref”)。
  • 已知到达prp 最多获得1 行。
  • pr 做了少量的过滤(active=1),但直到EXPLAIN 的第三行。并且没有索引可用于此过滤。这可以通过复合索引(active, id_prod_ref) 得到改善,但只能略微改善。由于只有 5-10% 被过滤掉,这不会有太大帮助。
  • 在所有JOINing 和过滤之后,将有两个 临时表和排序,一个用于GROUP BY,一个用于ORDER BY
  • 只有在那之后,才会从到目前为止收集的 70K(左右)行中剥离 10 行。

如果没有 ORDER BY,EXPLAIN 表明不同的顺序似乎更好。然后 tmp & sort 消失了。

1   SIMPLE  p   index   PRIMARY     4                     1   Using index
1   SIMPLE  pr  ref     id_prod     4   p.id_prod         2   Using where
1   SIMPLE  od  ref     id_prod_ref 4   pr.id_prod_ref   67  
1   SIMPLE  o   eq_ref  PRIMARY     4   dbne.od.id_order  1   Using where
  • p 中似乎只有 1 行,对吗?因此,在某种程度上,何时访问此表并不重要。当您拥有多个“产品”时,所有这些分析都可能会发生变化!
  • "key=PRIMARY", "Using index" 有点用词不当。它确实在使用数据,但能够有效地访问它,因为PRIMARY KEY 与数据“聚集”在一起。
  • 只有一个pr 行??也许优化器意识到不需要GROUP BY
  • 当它到达 od 时,它估计每个 p+pr 组合需要“67”行。
  • 你删除了ORDER BY,所以不需要排序,任意10行都可以投递。

【讨论】:

  • 嗨瑞克,感谢您的回复。实际上,我需要加入表产品和订单,因为我从它们中检索了一些值!即使使用您提到的查询,它仍然很慢,大约 0.7 秒:/ 如果我删除 ORDER BY,0.04 秒。我将使用 SHOW CREATE TABLE 编辑我的初始帖子
  • 知道您将获取哪些字段会有所作为!
  • 我已经用表格结构更新了我的帖子。希望现在一切都清楚了:)
  • 我彻底检查了我的答案。
  • 谢谢你,我明天去看看。不管结果如何,我要感谢你和所有的 stackoverflow 社区,感谢你在这件事上无可指责的帮助...... :) 干杯
猜你喜欢
  • 2019-09-12
  • 2021-11-17
  • 2012-12-31
  • 1970-01-01
  • 2019-01-21
  • 1970-01-01
  • 2017-02-11
  • 1970-01-01
  • 2013-05-30
相关资源
最近更新 更多