如何从SQL查询中删除临时文件和文件排序?

时间:2014-04-08 01:17:19

标签: mysql sql optimization indexing query-optimization

我一直在尝试在MySQL中创建索引,但每当我对查询运行解释时,都会不断获取临时文件。

我的表格的简化版本如下:

ordered_products
    op_id INT UNSIGNED NOT NULL AUTO_INCREMENT
    op_orderid INT UNSIGNED NOT NULL
    op_orderdate TIMESTAMP NOT NULL
    op_productid INT UNSIGNED NOT NULL

products
    p_id INT UNSIGNED NOT NULL AUTO_INCREMENT
    p_productname VARCHAR(128) NOT NULL
    p_enabled TINYINT NOT NULL

'ordered_products'表目前有超过1,000,000行,是所有已订购产品的记录,以及它们所属的订单。这张桌子快速增长。

“产品”表目前有大约3,000行,其中包含待售产品列表。

该网站显示特定时期(通常是过去3天)的顶级产品列表,我的查询如下:

SELECT COUNT(op.op_productid) AS ProductCount, op.op_productid
FROM ordered_products op
LEFT JOIN products p ON op.op_productid=p.p_id
WHERE op.op_orderdate>='2014-03-08 00:00:00'
AND p.p_enabled=1
GROUP BY op.op_productid
ORDER BY ProductCount DESC, p.p_productname ASC

当我运行该查询时,通常需要大约800毫秒(0.8秒)才能执行,这太荒谬了。我们已经通过缓存来解决这个问题,但是每当缓存过期时,我们都会放慢速度。我需要解决这个问题。

我试图对表进行索引,但无论我尝试什么,我都无法避免临时和文件排序。 EXPLAIN的输出是:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  p   index   PRIMARY,idx_enabled_id_name idx_enabled_id_name 782 \N  1477    Using where; Using index; Using temporary; Using filesort
1   SIMPLE  op  ref idx_pid_oid_date    idx_pid_oid_date    4   test_store.p.p_id   9   Using where; Using index

如果我删除了GROUP BY,则文件存档会消失,但是我需要它以确保ProductCount值显示每个产品计数而不是所有产品的总和。

如果我删除GROUP BY和ORDER BY ProductCount,临时和文件都会消失,但现在我的结果集非常糟糕。

任何人都可以帮我解决这个问题吗?我尝试了很多不同的索引,并尝试过多次重写SQL,但永远不会成功。

非常感谢任何帮助。

1 个答案:

答案 0 :(得分:1)

在计算列ORDER BY上使用ProductCount时,无法删除临时表和文件排序。计算列没有索引,因此必须在查询时进行排序。

我尝试通过实验重现您的结果。我可以在op_productid上放置一个索引,然后优化器可以使用它来执行GROUP BY

mysql> EXPLAIN SELECT COUNT(op.op_productid) AS ProductCount, op.op_productid 
FROM ordered_products op FORCE INDEX (op_productid) STRAIGHT_JOIN products p 
  ON op.op_productid=p.p_id 
WHERE op.op_orderdate>='2014-03-08 00:00:00' AND p.p_enabled=1 
GROUP BY op.op_productid ORDER BY null;

在我的情况下,我不得不使用STRAIGHT_JOIN和FORCE INDEX来覆盖优化器。但这可能是由于我的测试环境,我每个表只有1或2行进行测试,它会抛弃优化器的选择。在您的真实数据中,它可能是一个更明智的选择。

另外,如果WHERE子句中有条件使隐式连接成为内连接,则不要使用LEFT JOIN。了解联接的类型及其工作原理 - 默认情况下不要总是使用LEFT JOIN。

+----+-------------+-------+-------+---------------+--------------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key          | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+--------------+---------+------+------+-------------+
|  1 | SIMPLE      | op    | index | op_productid  | op_productid | 4       | NULL |    5 | Using where |
|  1 | SIMPLE      | p     | ALL   | PRIMARY       | NULL         | NULL    | NULL |    1 | Using where |
+----+-------------+-------+-------+---------------+--------------+---------+------+------+-------------+

您唯一的选择是存储非规范化表,其中计数保持不变。然后,如果您的缓存失败,则刷新缓存不是一个昂贵的查询。