1. 程式人生 > >MySQL查詢優化之group by的優化

MySQL查詢優化之group by的優化

原文地址:https://dev.mysql.com/doc/refman/5.7/en/group-by-optimization.html

譯文:

8.2.1.15 GROUP BY優化

滿足GROUP BY子句的最常用方法是掃描整個表並建立一個新的臨時表,其中每個組的所有行都是連續的,然後使用這個臨時表查詢組並應用聚合函式(如果有的話)。在一些情況下,MySQL可以做的更好,通過使用索引訪問的方法避免建立臨時表。

使用索引處理GROUP BY的前提條件是group by列引用的屬性都來自同一個索引,而且索引按照順序儲存它其中的鍵(例如,對於BTREE索引是正確的,但是對於雜湊索引則不是)。在一個查詢中,使用臨時表是否會被使用索引方法所代替取決於索引的哪一部分被使用、為這些部分指定的條件以及所選擇的聚合函式。

有兩種方法可以通過索引訪問執行GROUP BY查詢,下文會進行詳細的介紹。第一種方法將分組操作與所有範圍謂詞(如果有)一起應用。第二種方法先執行一個範圍掃描,然後對結果元組進行分組。

在MySQL中,GROUP BY用於排序,所以伺服器也可能對分組操作使用ORDER BY優化。但是,不建議依賴於隱式或顯式的GROUP BY分組。可以參考Section 8.2.1.14, “ORDER BY Optimization”.

鬆散索引掃描

處理GROUP BY最有效的方法是使用一個索引直接檢索分組列。對於這種訪問方法,MySQL使用了一些型別的索引中的鍵是有序得這個性質(例如,BTREE索引)。該屬性允許在索引中使用查詢組,而不必考慮滿足所有WHERE條件的索引中的所有鍵。這種訪問方法只考慮索引中的一小部分鍵,因此稱為鬆散索引掃描。當沒有WHERE子句時,鬆散索引掃描讀取的鍵數與組數相同,而組數可能是一個比所有鍵數小得多的數字。如果WHERE子句包含範圍謂詞(請參閱

Section 8.8.1, “Optimizing Queries with EXPLAIN”),那麼鬆散的索引掃描將查詢每個滿足範圍條件的組的第一個鍵,並再次讀取可能的最小鍵數。在下列情況下,這是可能的:

    1)查詢在單個表上;

    2)GROUP BY只列舉構成索引的最左邊的字首的列,沒有其他列。(如果查詢有一個DISTINCT子句,而不是GROUP BY,則所有不同的屬性都指的是構成索引最左邊字首的列)。例如,如果表t1有一個建立在(c1,c2,c3)上的索引且查詢中有GROUPBY c1,c這樣的語句,則可以使用鬆散索引掃描。

    3)在查詢列表中使用的唯一聚合函式(如果有的話)是MIN()和MAX(),它們都引用同一列。列必須在索引中,並且必須立即跟隨GROUP BY中的列。

    4)除了查詢中GROUP BY引用的索引列之外,索引的任何其他部分都必須是常量(也就是說,它們必須以與常量相等的方式引用),不包括MIN()或MAX()函式的引數。

    5)對於索引中的列,必須索引完整的列值,而不僅僅是字首。例如,對於c1 VARCHAR(20),INDEX (c1(10),索引只使用c1值的字首,不能用於鬆散的索引掃描。

如果鬆散掃描索引對一個查詢來說是適用的,explain的輸出結果中會在Extra列顯示Using index for group-by。

假設表t1(c1,c2,c3)上有一個索引idx(c1,c2,c3,c4)。鬆散索引掃描訪問方法可用於以下查詢:

  • 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;

以下查詢無法使用此快速查詢方法執行,原因如下:

    1)有除了MIN()和MAX()之外的聚合函式:

  • SELECT c1, SUM(c2) FROM t1 GROUP BY c1;

    2)GROUP BY子句中的列構不成索引最左邊的字首:

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

    3)查詢涉及到的部分鍵有在GROUP BY子句中的列後才出現的鍵(列),對於這個鍵,不存在與常量相等的情況:

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

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

除了已經支援的聚合函式MIN()和MAX()的引用之外,鬆散索引掃描訪問方法還可以應用於select列表中其他形式的聚合函式引用:

    1)支援AVG(DISTINCT),SUM(DISTINCT),和COUNT(DISTINCT)。AVG(DISTINCT)和SUM(DISTINCT)接受單一引數,COUNT(DISTINCT)可以接受多列作為引數;

    2)在查詢中必須沒有GROUP BY或者DISTINCT子句;

    3)前面描述的鬆散索引掃描限制仍然適用。

假設表t1(c1,c2,c3)上有一個索引idx(c1,c2,c3,c4)。鬆散索引掃描訪問方法可用於以下查詢:

  • 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子句中的鍵列的前面或中間。等式條件中的常量填充搜尋鍵中的任何“缺口”,這樣就可以形成索引的完整字首。這些索引字首可以用於索引查詢。如果分組後的結果需要排序,那麼它就可能形成作為索引字首的搜尋鍵,MySQL還避免了額外的排序操作,因為在有序索引中使用字首進行搜尋已經按順序檢索了所有鍵。

假設表t1(c1,c2,c3)上有一個索引idx(c1,c2,c3,c4)。以下查詢不使用前面描述的鬆散索引掃描訪問方法,但仍然使用嚴格索引掃描訪問方法。

    1)GROUP BY中有一個缺口,但被條件c2 = 'a'覆蓋:

  • SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;

    2)GROUP BY不以鍵的第一部分開始,但是有一個條件為該部分提供一個常量:

  • SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;

PS:由於水平有限,譯文中難免存在謬誤,歡迎批評指正。