我的答案是相當長的囉嗦,但我希望你會學到幾件事情。我給你兩個可能的改進。
「防止使用臨時表」和「防止'filesort'」。這些都不是真正的目標。真正的目標是更快的查詢。
GROUP BY one_thing
ORDER BY something_else
將總是(我認爲)至少需要一個TEMP和文件排序,有時是兩個。這只是實現你的目標的必要條件。
另一方面,支持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.
如果優化做事的順序給出的,它可以
- 使用索引開始時間戳過濾,並寫那到一個tmp表
- 排序做
GROUP BY
- 再次排序做
ORDER BY
。
(可能是我悲觀GROUP BY
處理是如何完成的。使用EXPLAIN FORMAT=JSON SELECT...
獲得更深入的瞭解。)
你提出了一個複合INDEX(timestamp, player_id)
。那麼,這將是沒有用的,因爲第一個部分用於範圍。想想這個:你有一長串人和他們的出生年。你想要所有那些姓氏以'B'開頭的人,並且你想按出生年份將他們分組。排列清單的最佳方式是什麼?所以你不會複製和排序它們?然後加上最常見的出生年份排序。
返回複合索引。作爲一般規則,如果您在「範圍」上下文中的索引中使用第一個列,則索引的其餘部分將不會使用。
因此,給定查詢最有用的索引僅僅是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
將需要排序。
「如何優化此查詢並防止使用臨時表?」 - 如果最快的方法是使用臨時表? –
@MitchWheat如果是這樣的話,那麼我誤導了我的問題 – JimmyBanks
@MitchWheat - 我建議_only way_是使用tmp表。 (請參閱我的回答) –