滿足 GROUP BY
子句最常見的方式是掃描整個資料表,並建立一個新的暫存表,其中每個群組的所有列都是連續的,然後使用這個暫存表來尋找群組並套用彙總函式(如果有的話)。在某些情況下,MySQL 能夠做得更好,並透過使用索引存取來避免建立暫存表。
使用索引進行 GROUP BY
的最重要先決條件是,所有 GROUP BY
欄都參考來自同一個索引的屬性,且索引以順序方式儲存其索引鍵(例如,BTREE
索引就是如此,但 HASH
索引則否)。是否能以索引存取取代暫存表的使用,也取決於查詢中使用索引的哪些部分、針對這些部分指定的條件,以及選取的彙總函式。
有兩種方式透過索引存取來執行 GROUP BY
查詢,如下節詳細說明。第一種方法將分組作業與所有範圍謂詞(如果有的話)一起套用。第二種方法則先執行範圍掃描,然後再將產生的元組分組。
在某些情況下,即使沒有 GROUP BY
,也可以使用寬鬆索引掃描。請參閱跳過掃描範圍存取方法。
處理 GROUP BY
最有效的方式是,使用索引直接擷取分組欄。透過此存取方法,MySQL 使用某些索引類型的屬性,即金鑰是有序的(例如,BTREE
)。此屬性允許在索引中使用查找群組,而無需考慮索引中滿足所有 WHERE
條件的所有索引鍵。此存取方法僅考慮索引中的一小部分索引鍵,因此稱為寬鬆索引掃描。當沒有 WHERE
子句時,寬鬆索引掃描會讀取與群組數目一樣多的索引鍵,這可能比所有索引鍵的數目小得多。如果 WHERE
子句包含範圍謂詞(請參閱range
聯結類型在第 10.8.1 節,「使用 EXPLAIN 優化查詢」中的討論),寬鬆索引掃描會查找滿足範圍條件的每個群組的第一個索引鍵,並再次讀取最小數量的索引鍵。在下列條件下,這是可能的
查詢適用於單一資料表。
GROUP BY
僅命名構成索引最左前綴的欄,沒有其他欄。(如果查詢具有DISTINCT
子句而非GROUP BY
,則所有不同的屬性都參考構成索引最左前綴的欄。)例如,如果資料表t1
在(c1,c2,c3)
上有索引,則如果查詢有GROUP BY c1, c2
,則寬鬆索引掃描適用。如果查詢有GROUP BY c2, c3
(這些欄不是最左前綴)或GROUP BY c1, c2, c4
(c4
不在索引中),則不適用。選取清單中使用的唯一彙總函式(如果有的話)是
MIN()
和MAX()
,而且它們都參考相同的欄。該欄必須在索引中,並且必須緊接在GROUP BY
中的欄之後。除了
MIN()
或MAX()
函式的引數之外,查詢中參考的GROUP BY
中的索引的其他部分必須是常數(也就是說,它們必須在與常數的等式中被參考)。索引中的欄位必須索引完整欄位值,而不僅僅是前綴。例如,對於
c1 VARCHAR(20), INDEX (c1(10))
,索引僅使用c1
值的前綴,因此不能用於鬆散索引掃描。
如果鬆散索引掃描適用於查詢,EXPLAIN
輸出會在 Extra
欄位中顯示 Using index for group-by
。
假設表 t1(c1,c2,c3,c4)
上有一個索引 idx(c1,c2,c3)
。鬆散索引掃描存取方法可用於以下查詢
SELECT c1, c2 FROM t1 GROUP BY c1, c2;
SELECT DISTINCT c1, c2 FROM t1;
SELECT c1, MIN(c2) FROM t1 GROUP BY c1;
SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2;
SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;
由於以下原因,以下查詢無法使用此快速選擇方法執行
除了已經支援的 MIN()
和 MAX()
引用之外,鬆散索引掃描存取方法還可以應用於選取清單中的其他形式的彙總函數引用
支援
AVG(DISTINCT)
、SUM(DISTINCT)
和COUNT(DISTINCT)
。AVG(DISTINCT)
和SUM(DISTINCT)
接受單一引數。COUNT(DISTINCT)
可以有多個欄位引數。查詢中不得有
GROUP BY
或DISTINCT
子句。先前描述的鬆散索引掃描限制仍然適用。
假設表 t1(c1,c2,c3,c4)
上有一個索引 idx(c1,c2,c3)
。鬆散索引掃描存取方法可用於以下查詢
SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1;
SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2, c1) FROM t1;
緊密索引掃描可能是完整索引掃描或範圍索引掃描,具體取決於查詢條件。
當不滿足鬆散索引掃描的條件時,仍然有可能避免為 GROUP BY
查詢建立暫存表。如果 WHERE
子句中有範圍條件,則此方法僅讀取滿足這些條件的索引鍵。否則,它會執行索引掃描。由於此方法讀取 WHERE
子句定義的每個範圍中的所有索引鍵,或者如果沒有範圍條件則掃描整個索引,因此稱為緊密索引掃描。使用緊密索引掃描,僅在找到所有滿足範圍條件的索引鍵後才執行分組操作。
為了使此方法有效,對於查詢中引用 GROUP BY
索引鍵之前或之間的索引鍵部分的所有欄位,存在常數相等條件就足夠了。來自相等條件的常數會填補搜尋索引鍵中的任何「間隙」,以便可以形成索引的完整前綴。然後,這些索引前綴可以用於索引查詢。如果 GROUP BY
結果需要排序,並且可以形成索引前綴的搜尋索引鍵,則 MySQL 還可以避免額外的排序操作,因為使用有序索引中的前綴進行搜尋已經會依序擷取所有索引鍵。
假設表 t1(c1,c2,c3,c4)
上有一個索引 idx(c1,c2,c3)
。以下查詢不適用於先前描述的鬆散索引掃描存取方法,但仍然適用於緊密索引掃描存取方法。
GROUP BY
中有一個間隙,但它被條件c2 = 'a'
覆蓋SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;
GROUP BY
並非以索引鍵的第一部分開始,但有一個條件為該部分提供了常數SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;