我一直在尝试在 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,但都没有成功。
如有任何帮助,我们将不胜感激。
最佳答案
在计算列 ProductCount
上使用 ORDER BY
时,您无法摆脱临时表和文件排序。计算列没有索引,因此它必须在查询时进行排序。
我尝试通过实验重现您的结果。我可以在 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 |
+----+-------------+-------+-------+---------------+--------------+---------+------+------+-------------+
你唯一的选择是存储一个非规范化的表,其中计数是持久的。然后,如果您的缓存失败,刷新缓存的查询就不是一个昂贵的查询。
关于mysql - 如何从我的 SQL 查询中删除临时文件和文件排序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22925918/