1. 程式人生 > >(七)group by 的優化

(七)group by 的優化

section bee mar pes 之前 cor username pro 官網

---
title: 不懂SQL優化?那你就OUT了(七)

MySQL如何優化--group by

date: 2018-12-15

categories: 數據庫優化
---

技術分享圖片

上一篇我們主要討論了 order by 的優化,這一篇我們繼續討論 group by 的優化。

group by子句在進行分組時一般的方法(如果沒有合適的索引可用)是先掃描整個表並創建一個新的臨時表,然後按照group by 指定的列進行排序, 在創建的臨時表中每個組的所有行應為連續的,然後使用該臨時表來找到組並執行聚合函數(如果有)。

例如:(與上一篇 測試 order by 數據一樣)

CREATE TABLE t_testGroupBy(

userid INT PRIMARY KEY AUTO_INCREMENT, -- 用戶編號

username VARCHAR(25), -- 用戶姓名

userAge INT, -- 用戶年齡

usergender CHAR(3), -- 用戶性別

provice VARCHAR(25), -- 所在省份

city VARCHAR(25), -- 所在城市

address VARCHAR(200) -- 詳細地址
);

測試數據:


INSERT INTO t_testGroupBy VALUES(NULL,‘張三‘,18,‘男‘,‘四川省‘,‘成都市‘,‘xxxx路222號‘);
INSERT INTO t_testGroupBy VALUES(NULL,‘李四‘,20,‘女‘,‘雲南省‘,‘昆明市‘,‘xxx北路12號‘);
INSERT INTO t_testGroupBy VALUES(NULL,‘王五‘,24,‘男‘,‘貴州省‘,‘遵義市‘,‘xxxxx路18號‘);
INSERT INTO t_testGroupBy VALUES(NULL,‘趙六‘,19,‘女‘,‘四川省‘,‘綿陽市‘,‘xx路234號‘);
INSERT INTO t_testGroupBy VALUES(NULL,‘孫琦‘,28,‘男‘,‘雲南省‘,‘玉溪市‘,‘xxxx路324號‘);
INSERT INTO t_testGroupBy VALUES(NULL,‘王曉琪‘,21,‘女‘,‘雲南省‘,‘玉溪市‘,‘xxxx路123號‘);


建立索引:


CREATE INDEX idx_name ON t_testgroupby(usergender);

CREATE INDEX idx_age ON t_testgroupby(userAge);

CREATE INDEX idx_provice_city_address ON t_testgroupby(provice,city,address);


例如:

查詢 男學生和女學生的總人數


技術分享圖片

可以從上圖中看到在進行group by 操作時,創建了臨時表和使用了文件排序,這樣的sql語句執行效率是很低的,需要優化。

使用group by查詢結果集時速度慢的原因可能有以下幾種:

1. 分組字段不在同一張表中

2. 分組字段沒有建索引

3. 分組字段導致索引沒有起作用(如何讓索引起作用才是關鍵)

4. 分組字段中使用聚合函數導致索引不起作用


在某些情況中,mysql能夠通過使用索引訪問而不用創建臨時表。

group by 與 order by 相比,group by主要只是多了排序之後的分組操作,如果在分組的時候還使用了其他的一些聚合函數,那麽還需要執行一些聚合函數的計算。所以,在 group by 的實現過程中,與order by 一樣也可以利用到索引。

<font style="color:coral">group by 使用索引的最重要的前提條件是所有group by列引用同一索引,並且索引按順序保存其關鍵字(例如,這是B+樹索引(有序的),而不是HASH索引(沒有順序的))</font>。

group by 是否使用索引訪問來代替臨時表的使用還取決於在查詢中使用了哪部分索引、為該部分指定的條件,以及選擇的聚合函數。

有兩種方法通過索引訪問執行group by的查詢:

在第1個方法(<font style=‘color:coral‘>松散索引掃描</font>)中,分組操作和範圍預測(如果有的話)一起執行完成的.

第2個方法(<font style=‘color:coral‘>緊湊索引掃描</font>))首先執行範圍掃描,然後組合結果元組(數據)。


###松散索引掃描(Loose Index Scan)


官網給出的定義:


> The most efficient way to process GROUP BY is when an index is used to directly retrieve the grouping columns. With this access method, MySQL uses the property of some index types that the keys are ordered (for example, BTREE). This property enables use of lookup groups in an index without having to consider all keys in the index that satisfy all WHERE conditions. This access method considers only a fraction of the keys in an index, so it is called a Loose Index Scan.

> When there is no WHERE clause, a Loose Index Scan reads as many keys as the number of groups, which may be a much smaller number than that of all keys. If the WHERE clause contains range predicates (see the discussion of the range join type in Section 8.8.1, “Optimizing Queries with EXPLAIN”), a Loose Index Scan looks up the first key of each group that satisfies the range conditions, and again reads the smallest possible number of keys

翻譯:

處理分組最有效方法是使用的索引直接跟分組列有關。使用這種訪問方法,MySQL使用一些索引類型的屬性(關鍵字),這些索引的類型鍵是有序的(理解的:這些關鍵字已經排好序)(例如,BTREE),該屬性允許在分組時使用,而不必考慮滿足所有WHERE條件的索引中的所有鍵(理解的:這使得索引中用於分組的字段不必完全涵蓋WHERE條件中索引對應的key)。這種訪問方法只考慮索引中鍵的一部分,因此稱為松散的索引掃描。


當沒有WHERE子句時,松散的索引掃描讀取的鍵與組的數量相同,組的數量可能比所有鍵的數量小得多.
如果WHERE子句包含範圍判斷(請參閱8.8.1節“使用EXPLAIN優化查詢”中關於範圍連接類型的討論),那麽松散的索引掃描將查找滿足範圍條件的每個組的第一個鍵,並再次讀取可能的最小鍵數。

#####使用松散索引掃描需要滿足以下條件:

1. 查詢針對一個單表。

2. group by 包括索引的第1個連續部分(使用聯合索引時,必須滿足最左前綴原則)(如果對於GROUP BY,查詢有一個DISTINCT子句,則所有顯式屬性指向索引開頭)。

3. 在使用group by 的同時,如果有聚合函數,只能使用 MAX 和 MIN 這兩個聚合函數,並且它們均指向相同的列。

4.如果引用(where條件中)到了該索引中GROUP BY 條件之外的字段條件的時候,必須以常量形式存在,但MIN()或MAX() 函數的參數例外。

補充:如果sql中有where子句,且select中引用了該索引中group by 條件之外的字段條件的時候,where中這些字段要以常量形式存在。如果查詢中有where條件,則條件必須為索引,不能包含非索引的字段。

此類查詢的EXPLAIN輸出顯示Extra列的Using indexforgroup-by。

以下是官網給出的案列:

下面的查詢提供該類的幾個例子,假定表t1(c1,c2,c3,c4)有一個索引idx(c1,c2,c3):

1. SELECT c1, c2 FROM t1 GROUP BY c1, c2;

技術分享圖片

2. SELECT DISTINCT c1, c2 FROM t1;

技術分享圖片

3.SELECT c1, MIN(c2) FROM t1 GROUP BY c1;

4.SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;

5.SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2;

6.SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;

7.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;(在其他數據庫中 group by中沒有的列不能出現在select 中)

###緊湊索引掃描(Tight Index Scan)

> A Tight Index Scan may be either a full index scan or a range index scan, depending on the query conditions.

When the conditions for a Loose Index Scan are not met, it still may be possible to avoid creation of temporary tables for GROUP BY queries. If there are range conditions in the WHERE clause, this method reads only the keys that satisfy these conditions. Otherwise, it performs an index scan. Because this method reads all keys in each range defined by the WHERE clause, or scans the whole index if there are no range conditions, it is called a Tight Index Scan. With a Tight Index Scan, the grouping operation is performed only after all keys that satisfy the range conditions have been found.

翻譯:

緊湊索引掃描可以為索引掃描或一個範圍索引掃描,取決於查詢條件。

當松散索引掃描條件沒有滿足的時候,group by仍然有可能避免創建臨時表。如果where條件有範圍掃描,那麽緊湊索引掃描僅讀取滿足這些條件的keys(索引鍵),否則執行全索引掃描。這種方式讀取所有where條件定義的範圍內的索引鍵,或者掃描整個索引,因而稱作緊湊索引掃描。請註意對於緊湊式索引掃描,只有找到了滿足範圍條件的所有關鍵字後才進行組合操作。


在 mysql 中,mysql query Optimizer 首先會選擇嘗試通過松散索引掃描來實現 group by 操作,當發現某些情況無法滿足松散索引掃描實現 group by 的要求之後,才會嘗試通過緊湊索引掃描來實現。


以下查詢不適用於之前描述的松散索引掃描訪問方法,但仍然可以使用緊湊索引掃描訪問方法。

1.GROUP BY有一個間隔,但它被覆蓋的條件為c2 = ‘a’

例如:SELECT c1,c2,c3 FROM t1 WHERE c2 = ‘a‘ GROUP BY c1,c3;

技術分享圖片

2.GROUP BY不以關鍵字的第1個元素開始(沒有遵循最左前綴原則),但是有一個條件提供該元素的常量:

例如:SELECT c1,c2,c3 FROM t1 WHERE c1 = ‘a‘ GROUP BY c2,c3;

技術分享圖片


當 group by 條件字段並不連續或者不是索引前綴部分的時候,mysql查詢優化器無法使用松散索引掃描,所以無法直接通過索引完成group by操作,因為缺失的索引鍵信息無法得到。但是,如果查詢語句中存在一個常量值來引用缺失的索引鍵,則可以使用緊湊索引掃描完成分組操作,因為常量填充了搜索關鍵字中的“間隔”,可以形成完整的索引前綴,這些索引前綴可以用於索引查找。

(七)group by 的優化