2011-05-20 161 views
6

數據庫是MySQL與MyISAM引擎。GROUP BY查詢優化

表定義:

CREATE TABLE IF NOT EXISTS matches (
    id int(11) NOT NULL AUTO_INCREMENT, 
    game int(11) NOT NULL, 
    user int(11) NOT NULL, 
    opponent int(11) NOT NULL, 
    tournament int(11) NOT NULL, 
    score int(11) NOT NULL, 
    finish tinyint(4) NOT NULL, 
    PRIMARY KEY (id), 
    KEY game (game), 
    KEY user (user), 
    KEY i_gfu (game , finish , user) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=3149047 ; 

我已成立的指數上(game, finish, user)但這GROUP BY查詢仍然需要0.4 - 0.6秒內運行:

SELECT user AS player 
    , COUNT(id) AS times 
FROM matches 
WHERE finish = 1 
    AND game = 19 
GROUP BY user 
ORDER BY times DESC 

EXPLAIN輸出:

| id | select_type | table | type | possible_keys | key | key_len | 
| 1 | SIMPLE  | matches | ref | game,i_gfu | i_gfu | 5 | 

| ref  | rows | Extra          | 
| const,const | 155855 | Using where; Using temporary; Using filesort | 

有什麼辦法可以讓它更快?該表有大約800K條記錄。


編輯:我改變COUNT(id)COUNT(*)的時間下降到0.08 - 0.12秒。我想我已經嘗試過在製作索引之前忘了在之後再次更改索引。

在解釋輸出的使用索引解釋的加快:

| rows | Extra             | 
| 168029 | Using where; Using index; Using temporary; Using filesort | 

(方的問題:這是丟棄的5倍的正常)

有大約2000個用戶,所以最後的排序,即使它使用filesort,也不會影響性能。我嘗試沒有ORDER BY,它仍然需要幾乎相同的時間。

+5

計數(\ *)的性能比count(id)快得多的原因是MySQL對count(\ *)情況進行了特定的優化。計數(id)大小寫將通過數據第二遍來檢索結果,其中計數(\ *)使用現有的內部行計數器。儘可能使用計數(\ *)。 – 2011-05-20 15:30:43

回答

1

EXPLAIN驗證在查詢中使用了(game, finish, user)索引。這對我來說似乎是最好的索引。這可能是硬件問題嗎?什麼是您的系統RAM和CPU?

+0

內存爲1GB。 CPU是(我認爲)AMD皓龍四核3.5GHz。 – 2011-05-20 13:20:31

+0

我猜你的瓶頸是內存。我建議將其提高到4GB。 – ic3b3rg 2011-05-20 13:24:03

+0

4Gb處理900k行〜每個30字節的表? ;)這甚至不是30兆字節;) – matt 2011-05-20 14:08:43

7

擺脫'遊戲'鍵 - 它與'i_gfu'是多餘的。由於'id'是唯一的count(id)只返回每個組中的行數,所以你可以去掉它並用count(*)替換它。嘗試一下並粘貼EXPLAIN輸出:

SELECT user AS player, COUNT(*) AS times 
FROM matches 
WHERE finish = 1 
AND game = 19 
GROUP BY user 
ORDER BY times DESC 
2

呃,強硬。嘗試對您的索引進行重新排序:首先放置user列(因此索引(user, finish, game)),因爲這會增加GROUP BY可以使用索引的機率。但是,如果限制用於MIN和MAX的聚合函數(參見http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.htmlhttp://dev.mysql.com/doc/refman/5.5/en/loose-index-scan.html),通常GROUP BY只能使用索引。你的訂單也沒有幫助。

+0

我試過這個索引,也嘗試過'(用戶,遊戲,完成)',並強制使用它,但它更慢。 – 2011-05-20 13:07:29

+0

奇數。我知道你不能用GROUP BY和ORDER BY的組合來做得更好:如果查詢速度太慢,你可能想創建一個明確的聚合表。使用filesort *顯示的事實表明ORDER BY不能從任何索引完成:也許嘗試將「id」添加到索引? – Femi 2011-05-20 13:15:45

+0

你的意思是一個'(遊戲,完成,用戶,id)'索引? – 2011-05-20 14:13:17

1

我認爲,大部分時間都花在提取和更重要的排序上(兩次,包括通過讀索引跳過的),從800k中排出150k行。我懷疑你可以比現在更多地優化它。

+0

提取,是的。排序不,它不花費時間排序。 – 2011-05-20 13:03:02

+0

這不是你的查詢計劃建議的。也不是你的問題,就此而言。他們都說至少需要一種。 :-) – 2011-05-20 13:04:18

+0

我的意思是,花費在分類上的時間與分類花費的時間相比非常短。 – 2011-05-20 13:11:21

1

正如其他人所指出的,您可能已經達到了調整查詢本身的能力的極限。接下來應該看看服務器中的變量max_heap_table_sizetmp_table_size的設置。默認值是16MB,對於你的表來說可能太小了。

+0

thnx的建議,兩個設置都在64M。 – 2011-05-20 13:42:30

1

此查詢的缺點之一是您按彙總排序。這意味着在生成完整的結果集之前您不能返回任何行;沒有索引可以存在(對於MySQL myisam,無論如何)來解決這個問題。

雖然可以非常容易地將數據非規範化以克服此問題;例如,您可以添加一個插入/更新觸發器,以將計數值粘貼到彙總表中,並帶有索引,以便您可以立即開始返回行。