我有以下sql查询
SELECT a.player_id, COUNT( a.player_id ) AS views, b.firstname, b.lastname, b.link_id
FROM buyout_calculator_query AS a
LEFT JOIN player AS b ON ( a.player_id = b.player_id )
WHERE a.timestamp >259200
GROUP BY player_id
ORDER BY views DESC
我有以下索引,它基于我见过的各种堆栈答案:
CREATE INDEX timestamp_playerid_index ON buyout_calculator_query(
timestamp,
player_id
)
我的错误理解是这会阻止临时表,因为时间戳是约束,按player_id分组,并由player_id求和
但是,在运行explain时,这就是我所看到的(临时表):
Possible Keys: timestamp_playerid_index
Key: timestamp_playerid_index
Extra: Using where; Using index; Using temporary; Using filesort
如何优化此查询并阻止使用临时表?
答案 0 :(得分:1)
我的答案相当漫长,但我希望你能学到很多东西。我给你两个可能的改进。
“防止使用临时表”和“防止'文件输出'”。这些都不是真正的目标。真正的目标是更快的查询。
GROUP BY one_thing
ORDER BY something_else
总是(我认为)至少需要一个temp和filesort,有时候需要两个。只需要实现你的目标。
另一方面,支持SELECT
所需的temp + filesort不一定是基于磁盘的“文件”。它通常只是一个内存中的数据集(实际上是MEMORY
表)。
让我们进一步了解你所拥有的东西:
Filter on a.timestamp -- but a "range"
GROUP BY a.player_id
ORDER BY an aggregate -- not know up front, so no way to use an index.
如果优化器按照给定的顺序执行操作,则可以
GROUP BY
ORDER BY
。(我可能对GROUP BY
处理工作的方式感到悲观。使用EXPLAIN FORMAT=JSON SELECT...
获取更多信息。)
您建议使用复合INDEX(timestamp, player_id)
。好吧,因为第一个部分用于范围,所以没有用。想一想:你有很多人和他们的出生年份。并且你想要所有姓氏以'B'开头并且你想按出生年份分组的人。安排列表的最佳方法是什么,这样你就不会复制东西并对它们进行排序?然后按最常见的出生年份进行排序。
返回复合索引。作为一般规则,如果您在“范围”上下文中使用索引中的 first 列,则索引的其余部分将不使用。
因此,给定查询最有用的索引仅为INDEX(timestamp)
。 更正:INDEX(timestamp, player_id)
更好,因为它是一个“覆盖索引”,因此可以避免进入数据。 EXPLAIN
为您提供Using index
的线索。
请为这两个表提供SHOW CREATE TABLE
;我不得不从这里猜出来......
我想player
有PRIMARY KEY(player_id)
,对吗?
您正在使用LEFT
,因为买断查询会引用不存在的玩家?似乎不太可能,所以我猜你没有正当理由加上LEFT
。
另外,我猜您没有正当理由说COUNT(a.player_id)
而不是COUNT(*)
。
一旦摆脱了LEFT
,我们就可以尝试另一种查询形式:
SELECT b.player_id,
( SELECT COUNT(*)
FROM buyout_calculator_query
WHERE player_id = b.player_id
AND timestamp >259200
) AS views,
b.firstname, b.lastname, b.link_id
FROM player AS b
ORDER BY views DESC
看看它是否运行得更快。它有一个“相关子查询”,但避免使用GROUP BY
。请将其添加到buyout_calculator_query
:INDEX(player_id, timestamp)
。
更进一步,这可能(或可能不)更好:
SELECT b.player_id, a.views, b.firstname, b.lastname, b.link_id
FROM
( SELECT player_id, COUNT(*) AS views
FROM buyout_calculator_query
WHERE timestamp >259200
GROUP BY player_id
) AS a
JOIN player AS b USING(player_id)
ORDER BY a.views DESC
如果您有INDEX(player_id, timestamp)
,这将是“使用索引”;通过避免索引和数据之间的弹跳,这是一个额外的提升。此外,子查询不需要tmp表,也不需要filesort。但是子查询生成一个tmp表,ORDER BY
需要排序。