2015-06-21 63 views
0

我有以下SQL查詢優化組由ORDER BY

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時,這就是我所看到的(temp表):

Possible Keys: timestamp_playerid_index 
Key: timestamp_playerid_index 
Extra: Using where; Using index; Using temporary; Using filesort 

如何優化此查詢並防止使用臨時表?

+0

「如何優化此查詢並防止使用臨時表?」 - 如果最快的方法是使用臨時表? –

+0

@MitchWheat如果是這樣的話,那麼我誤導了我的問題 – JimmyBanks

+0

@MitchWheat - 我建議_only way_是使用tmp表。 (請參閱我的回答) –

回答

1

我的答案是相當長的囉嗦,但我希望你會學到幾件事情。我給你兩個可能的改進。

「防止使用臨時表」和「防止'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. 

如果優化做事的順序給出的,它可以

  1. 使用索引開始時間戳過濾,並寫那到一個tmp表
  2. 排序做GROUP BY
  3. 再次排序做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這兩個表;我不得不從這裏猜出...

我想playerPRIMARY 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_queryINDEX(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將需要排序。