[英]Proper Indexing/Optimization of a MySQL GROUP BY and JOIN Query
我已經做了很多閱讀和谷歌搜索,我找不到任何滿意的答案,所以我很感激任何幫助。 我找到的大多數答案都接近我的情況,但沒有解決它(並試圖遵循解決方案並沒有給我任何好處)。
請參閱下面的編輯#2以獲得最佳示例
[這是最初的問題,但不能代表我的要求。]
假設我有2個表,每個表有4列:
我想執行以下查詢:
SELECT t.c1, t.c2, COUNT(*)
FROM test1 t
LEFT JOIN test2 t2 ON t2.key = t.key
GROUP BY t.c1, t.c2
兩個key
段都被索引為主鍵。 我想獲得每個c1,c2組中返回的行數。
當我解釋這個查詢時,我得到“使用臨時;使用filesort”。 我正在執行此查詢的實際表超過500,000行,這意味着它是一個耗時的查詢。
所以我的問題是(假設我在查詢中沒有做任何錯誤):有沒有辦法索引這個表來消除臨時/ filesort用法?
在此先感謝您的幫助。
編輯
這是表定義(在這個例子中,兩個表都是相同的 - 實際上它們不是,但我不確定它在這一點上有所不同):
CREATE TABLE `test1` (
`key` int(11) NOT NULL auto_increment,
`c1` date NOT NULL,
`c2` varchar(3) NOT NULL,
`c3` varchar(3) NOT NULL,
PRIMARY KEY (`key`),
UNIQUE KEY `c1` (`c1`,`c2`),
UNIQUE KEY `c2_2` (`c2`,`c1`),
KEY `c2` (`c2`,`c3`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
完整的EXPLAIN聲明:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t ALL NULL NULL NULL NULL 2 Using temporary; Using filesort
1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 tracking.t.key 1 Using index
這僅適用於我的示例表。 在我的真實表中,t的行表示500,000+(表中的每一行,盡管這可能與其他內容有關)。
編輯#2
這是一個更具體的例子來更好地解釋我的情況。
假設我有小聯盟棒球比賽的數據。 我有兩張桌子。 一個人掌握游戲數據:
CREATE TABLE `ex_games` (
`game_id` int(11) NOT NULL auto_increment,
`home_team` int(11) NOT NULL,
`date` date NOT NULL,
PRIMARY KEY (`game_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
另一個包含每場比賽中擊球的數據:
CREATE TABLE `ex_atbats` (
`ab_id` int(11) NOT NULL auto_increment,
`game` int(11) NOT NULL,
`team` int(11) NOT NULL,
`player` int(11) NOT NULL,
`result` tinyint(1) NOT NULL,
PRIMARY KEY (`hit_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
所以我有兩個問題。 讓我們從簡單的版本開始:我想返回一個游戲列表,其中包含每個游戲中有多少個游戲。 所以我想我會這樣做:
SELECT date, home_team, COUNT(h.ab_id) FROM `ex_atbats` h
LEFT JOIN ex_games g ON g.game_id = h.game
GROUP BY g.game_id
此查詢使用filesort / temporary。 有沒有更好的方法來構建這個或索引表來擺脫它?
然后,更棘手的部分:說我現在不僅要包括蝙蝠數量的計數,而且還要包括一個蝙蝠數量的計數,這些蝙蝠之前是同一個團隊的同一結果。 我認為這將是這樣的:
SELECT g.date, g.home_team, COUNT(ab.ab_id), COUNT(ab2.ab_id) FROM `ex_atbats` ab
LEFT JOIN ex_games g ON g.game_id = ab.game
LEFT JOIN ex_atbats ab2 ON ab2.ab_id = ab.ab_id - 1 AND ab2.result = ab.result
GROUP BY g.game_id
這是構造該查詢的正確方法嗎? 這也使用filesort / temporary。
那么完成這些任務的最佳方式是什么?
再次感謝。
短語Using temporary/filesort
通常與JOIN
操作中使用的索引無關。 有許多示例可以設置所有索引(它們顯示在EXPLAIN
中的key
和key_len
列中),但仍然可以Using temporary
和Using filesort
。
查看手冊中有關Using temporary
和Using filesort
:
為GROUP BY
子句中使用的所有列組合索引可能有助於在某些情況下擺脫Using filesort
。 如果您還發出ORDER BY
,則可能需要添加更復雜的索引。
首先,表格的定義很重要。 使用兩個主鍵連接是一回事,另一個是使用一側的主鍵和另一側的非唯一鍵進行連接等等。同樣重要的是,表格使用哪種類型的引擎,因為InnoDB以不同於MyISAM的方式處理主鍵發動機。
我注意到的是,在表test1
, (c1,c2)
組合是唯一的,並且字段不可為空。 這允許您的查詢被重寫為:
SELECT t.c1, t.c2, COUNT(*)
FROM test1 t
LEFT JOIN test2 t2 ON t2.key = t.key
GROUP BY t.key
當為JOIN
和GROUP BY
使用相同的字段時,它將給出相同的結果。 請注意,MySQL允許您在不在GROUP BY
列表中的SELECT
列表字段中使用,而不對它們使用聚合函數。 這在大多數其他系統中是不允許的,並且被某些人視為錯誤。 在這種情況下,雖然這是一個非常好的功能。 每一行都可以由(key)
或(c1,c2)
標識,因此兩個中的哪一個用於分組無關緊要。
另外需要注意的是,當你使用LEFT JOIN
,通常使用右側的連接列進行計數: COUNT(t2.key)
而不是COUNT(*)
。 對於test1
中沒有匹配test2
任何記錄的記錄,原始查詢將在該列中給出1
,因為它計算行數,而您可能想要計算test2
的相關記錄 - 並在這些情況下顯示0
。
所以,試試這個查詢並發布EXPLAIN:
SELECT t.c1, t.c2, COUNT(t2.key)
FROM test1 t
LEFT JOIN test2 t2 ON t2.key = t.key
GROUP BY t.key
對於innodb,它將起作用,因為默認情況下索引會篡改您的主鍵。 對於myisam,您必須擁有密鑰,因為索引的最后一列是“key”。 這將為優化器提供相同順序的所有鍵,並且他可以跳過排序。 你不能對索引前綴theN做任何范圍查詢,直接回到filesort。 目前正在努力解決類似的問題
索引有助於加入,但您仍需要進行完整排序才能執行分組。 從本質上講,它仍然必須處理集合中的每個記錄。
當然,添加where子句並限制集合會更快。 它只是不會得到你想要的結果。
除了在整個表上執行分組之外,可能還有其他選項。 我注意到你正在做一個SELECT * - 你想要從查詢中得到什么?
SELECT DISTINCT c1,c2 FROM test t LEFT JOIN test2 t2 ON t2.key = t.key
例如, 可能會跑得更快。 (我意識到這只是一個示例查詢,但是要知道當你不知道最終目標是什么時很難優化!)
編輯 - 在做一些閱讀(http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html)時,我了解到,在正確的情況下,索引可以顯着幫助組通過。
我所看到的是它需要是一個排序索引(如BTREE),而不是HASH。 也許:
CREATE INDEX c1c2 IN t (c1, c2) USING BTREE;
可能有幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.