文件首頁
MySQL 8.4 參考手冊
相關文件 下載本手冊
PDF (美式 Letter) - 39.9Mb
PDF (A4) - 40.0Mb
手冊頁 (TGZ) - 258.5Kb
手冊頁 (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


10.2.1.17 GROUP BY 優化

滿足 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 僅命名構成索引最左前綴的資料行,而不命名其他資料行。(如果查詢不是使用 GROUP BY,而是使用 DISTINCT 子句,則所有不同的屬性都會參考構成索引最左前綴的資料行。)例如,如果資料表 t1(c1,c2,c3) 上有索引,則如果查詢具有 GROUP BY c1, c2,則適用寬鬆索引掃描。如果查詢具有 GROUP BY c2, c3(資料行不是最左前綴)或 GROUP BY c1, c2, c4c4 不在索引中),則不適用。

  • 在選取清單中使用的唯一彙總函數(如果有的話)是 MIN()MAX(),而且它們都參照同一個欄位。該欄位必須在索引中,並且必須緊接在 GROUP BY 中的欄位之後。

  • 除了 MIN()MAX() 函數的引數之外,查詢中參照的索引任何其他部分,都必須是常數(也就是說,它們必須在與常數的相等條件中被參照)。

  • 對於索引中的欄位,必須索引完整的欄位值,而不僅僅是前綴。例如,使用 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() 之外的彙總函數

    SELECT c1, SUM(c2) FROM t1 GROUP BY c1;
  • GROUP BY 子句中的欄位沒有形成索引的最左邊前綴

    SELECT c1, c2 FROM t1 GROUP BY c2, c3;
  • 查詢參照了位於 GROUP BY 部分之後的索引部分,並且該部分與常數之間沒有相等條件

    SELECT c1, c3 FROM t1 GROUP BY c1, c2;

    如果查詢包含 WHERE c3 = const,則可以使用鬆散索引掃描。

除了已支援的 MIN()MAX() 參照之外,鬆散索引掃描存取方法還可應用於選取清單中其他形式的彙總函數參照

假設表 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;