GROUP BY + ORDER BY使我的查询非常慢

时间:2017-02-22 19:22:24

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

我试图找出我应该对我的查询和/或我的表格结构做些什么,以改进查询以获得在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的EXPLAIN

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,......)但是没有设法导出它。但我现在不认为它至关重要!

2 个答案:

答案 0 :(得分:0)

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

中的值

并使用group by作为select中的值(对于join来说是相同的,因为内部联接的值相等,并且pr的值不会为select结果重新执行)

 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  

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

答案 1 :(得分:0)

(在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已删除为无关紧要。

SUM()JOIN一起使用时要小心GROUP BY - 您可能会获得错误的,夸大的价值。

一张桌子的改进:

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_order_status的{​​{1}}索引。注意:(id_status, id_order)含蓄地添加到任何辅助密钥。
  • 估计需要扫描 76K(对于> 11)。
  • 在处理的某个地方,会有一个临时表和一种它。这可能或者可能不涉及磁盘I / O.
  • PRIMARY KEY的覆盖范围可能会找到1行,可能会找到0或超过1(" ref")。
  • 到达odpr时,最多可获得1行。
  • p进行少量过滤(pr),但直到active=1的第三行。并且没有索引对此过滤有用。这可以通过复合索引EXPLAIN进行改进,但只能稍微改进一下。只有5-10%被过滤掉,这不会有太大帮助。
  • 完成所有(active, id_prod_ref)和过滤后,会有两个临时表和排序,一个用于JOINing,一个用于GROUP BY
  • 只有在那之后,才会从收集到的70K(左右)行中剥离10行。

如果没有ORDER BY,ORDER BY会显示不同的顺序似乎更好。而tmp&排序消失了。

EXPLAIN
  • 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 似乎只有1行,对吗?因此,在某种程度上,访问此表时无关紧要。当你有多个"产品"所有这些分析都可能改变!
  • " key = PRIMARY","使用索引"有点用词不当。它实际上是使用数据,但能够有效地访问它,因为p是"集群"与数据。
  • 只有一个PRIMARY KEY行?也许优化器意识到不需要pr
  • 当它到达GROUP BY时,估计" 67"每个p + pr组合将需要行。
  • 您删除了od,因此无需排序,任何 10行都可以投放。