1. 程式人生 > >18.MySQL優化GROUP BY Optimization

18.MySQL優化GROUP BY Optimization

介紹

最常用的優化 GROUP BY 的方式就是掃描整張表然後建立臨時表在臨時表中保證所有資料分成不同的組,每個組裡的資料都是連續的,然後利用臨時表來發現組並應用聚合函式(假如有的話)。在某些情況下,MySQL可以做的更好,它可以使用索引避免使用臨時表。

使用索引優化GROUP BY的一個非常重要的先決條件是,GROUP BY中引用的屬性需要在同一個索引中,並且索引是有序的(舉個例子,可以使用Btree索引,但不能使用Hash索引)。是否使用索引掃描替換臨時表取決於在一個查詢中使用索引的哪個部分,為這些部分指定的條件以及所選的聚合函式。

如下所述,有兩種方法可以通過索引執行GROUP BY查詢。。第一種方法將分組操作與所有範圍謂詞(如果有)一起應用。第二種方法首先執行範圍掃描,然後對生成的元組進行分組。

  • Loose Index Scan
  • Tight Index Scan

Loose Index Scan在沒有GROUP BY的情況下也可以使用鬆散索引掃描

Loose Index Scan

一個最有效的方式優化GROUP BY就是索引直接掃描GROUP BY的列。使用此方法訪問,使用了索引的鍵的有序性這個屬性(Btree索引)。此屬性允許在索引中使用查詢組,而不必考慮索引中滿足所有條件的所有鍵。如果沒有WHERE 子句,則鬆散索引掃描會讀取與group數一樣多的keys,這可能比所有keys的數量小得多。如果一個WHERE條件包含範圍掃描的謂詞,一個Loose Index Scan會在滿足WHERE條件的group中查詢每個組的第一個keys,並再次讀取儘可能少的鍵。它可能按照以下步驟進行:

  • 在一個表上查詢
  • 這個GROUP BY僅僅使用一個索引的最左字首命名不包含其他列。(如果GROUP BY查詢具有DISTINCT子句,則所有不同的屬性引用形成索引的最左字首的列。)舉個例子,假如有一張表t1包含索引(c1,c2,c3),Loose Index Scan可以應用於GROUP BY c1,c2但是不能應用於GROUP BY c2,c3(因為這倆不是最左字首),也不能用於GROUP BY c1,c2,c4(c4不在索引中)。
  • 在select列表中的唯一的聚合函式,並且它們都引用同一列。這個列必須在index中,並且僅僅跟著GROUP BY的列。
  • 索引中除查詢中引用的GROUP BY之外的任何其他部分必須是常量。(也就是說,他們必須與常量相等),除了除了MIN()或MAX()函式的引數。
  • 在索引中的列,整行的值都必須在索引中,不能是字首。舉個例子,c1是VARCHAR(20),索引是 (c1(10)), 這個索引用了c1的一部分字首,所以無法使用索引。

如果使用了Loose Index Scan來優化了查詢,那麼 EXPLAIN 那麼Extra中會顯示 Using index for group-by 。

這有個索引idx(c1,c2,c3)在表t1(c1,c2,c3,c4)。 Loose Index Scan可以用於以下查詢:

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() or MAX():

      SELECT c1, SUM(c2) FROM t1 GROUP BY c1;
    
  • 不遵循索引的最左原則

      SELECT c1, c2 FROM t1 GROUP BY c2, c3;
    
  • 查詢引用了GROUP BY部分之後的鍵的一部分,並且與常量不相等:

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

    當增加條件WHERE c3 = const, Loose Index Scan可以被使用

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

  • 支援AVG(DISTINCT), SUM(DISTINCT)和 COUNT(DISTINCT)。AVG(DISTINCT) 並SUM(DISTINCT)有一個列。 COUNT(DISTINCT)可以有多個列。
  • 查詢中必須沒有GROUP BY或DISTINCT子句。
  • 以上描述的鬆散索引限制仍然適用。

這有個索引idx(c1,c2,c3)在表t1(c1,c2,c3,c4)。 Loose Index Scan可以用於以下查詢:

SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1;

SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2, c1) FROM t1;

Tight Index Scan

Tight Index Scan可以使索引全掃描,也可以是索引範圍掃描,這取決於查詢條件。

如果不滿足Loose Index Scan的條件,也可以避免GROUP BY的時候建立臨時表。假如再WHERE條件裡有範圍條件,這個方法僅讀取滿足那些條件的keys。否則,他執行索引掃描。因為這個方法讀取每個WHERE條件中每一個範圍條件的所有keys,或者掃描整個索引假如這個SQL離不包含範圍條件,這樣的查詢模式可以叫做Tight Index Scan。在Tight Index Scan的掃描方式中,僅在找到grouping中的所有keys之後才執行分組操作。

想要這個優化方式起作用,對於查詢中的所有列,只要有一個常數相等條件就足夠了,該條件是指在GROUP BY的列的部分之前或部分之間未出現的列被定義為常量。一個常數的等價條件用來填充"gaps"用來形成所以的完整字首。這個索引字首可以用於索引查詢。假如GROUP BY的查詢結果需要排序,並且可以形成作為索引字首的搜尋鍵,MySQL還避免了額外的排序操作,因為在有序索引中使用字首進行搜尋已經按順序檢索了所有鍵。

假設idx(c1,c2,c3)表上 有索引 t1(c1,c2,c3,c4)。以下查詢不適用於前面介紹的鬆散索引掃描訪問方法,但仍可使用緊密索引掃描訪問方法。

  • 這有一個gap,但是他通過c2='a’填補上了

      SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;
    
  • GROUP BY的條件不是從索引的第一列開始,但是通過c1 = 'a’填補上了條件

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