由於GROUP BY 實際上也同樣會進行排序操作,而且與ORDER BY 相比,GROUP BY 主要只是多了排序之後的分組操作。當然,如果在分組的時候還使用了其他的一些彙總函式,那麼還需要一些彙總函式的計算。所以,在GROUP BY 的實現過程中,與 ORDER BY 一樣也可以利用到索引。
在MySQL 中,GROUP BY 的實現同樣有多種(三種)方式,其中有兩種方式會利用現有的索引資訊來完成 GROUP BY,另外一種為完全無法使用索引的情境下使用。下面我們分別針對這三種實現方式做一個分析。
1、使用鬆散(Loose)索引掃描實現 GROUP BY
何謂鬆散索引掃描實現 GROUP BY 呢?實際上就是當 MySQL 完全利用索引掃描來實現 GROUP BY 的時候,並不需要掃描所有滿足條件的索引鍵即可完成操作得出結果。
下面我們通過一個樣本來描述鬆散索引掃描實現 GROUP BY,在樣本之前我們需要首先調整一下 group_message 表的索引,將 gmt_create 欄位添加到 group_id 和 user_id 欄位的索引中:
sky@localhost: example 08:49:45> create index idx_gid_uid_gc -> on group_message(group_id,user_id,gmt_create); Query OK, rows affected (0.03 sec) Records: 96 Duplicates: 0 Warnings: 0sky@localhost: example 09:07:30> drop index idx_group_message_gid_uid -> on group_message; Query OK, 96 rows affected (0.02 sec) Records: 96 Duplicates: 0 Warnings: 0
然後再看如下 Query 的執行計畫:
sky@localhost: example 09:26:15> EXPLAIN -> SELECT user_id,max(gmt_create) -> FROM group_message -> WHERE group_id < 10 -> GROUP BY group_id,user_id\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: group_message type: range possible_keys: idx_gid_uid_gc key: idx_gid_uid_gc key_len: 8 ref: NULL rows: 4 Extra: Using where; Using index for group-by
我們看到在執行計畫的 Extra 資訊中有資訊顯示“Using index for group-by”,實際上這就是告訴我們,MySQL Query Optimizer 通過使用鬆散索引掃描來實現了我們所需要的 GROUP BY 操作。
下面這張圖片描繪了掃描過程的大概實現:
要利用到鬆散索引掃描實現 GROUP BY,需要至少滿足以下幾個條件:
◆GROUP BY 條件欄位必須在同一個索引中最前面的連續位置;
◆在使用GROUP BY 的同時,只能使用 MAX 和 MIN 這兩個彙總函式;
◆如果引用到了該索引中 GROUP BY 條件之外的欄位條件的時候,必須以常量形式存在;
為什麼鬆散索引掃描的效率會很高?
因為在沒有WHERE子句,也就是必須經過全索引掃描的時候, 鬆散索引掃描需要讀取的索引值數量與分組的組數量一樣多,也就是說比實際存在的索引值數目要少很多。而在WHERE子句包含範圍判斷式或者等值運算式的時候, 鬆散索引掃描尋找滿足範圍條件的每個組的第1個關鍵字,並且再次讀取儘可能最少數量的關鍵字。
2.使用緊湊(Tight)索引掃描實現 GROUP BY
緊湊索引掃描實現 GROUP BY 和鬆散索引掃描的區別主要在於他需要在掃描索引的時候,讀取所有滿足條件的索引鍵,然後再根據讀取惡的資料來完成 GROUP BY 操作得到相應結果。
sky@localhost : example 08:55:14> EXPLAIN -> SELECT max(gmt_create) -> FROM group_message -> WHERE group_id = 2 -> GROUP BY user_id\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: group_message type: ref possible_keys: idx_group_message_gid_uid,idx_gid_uid_gc key: idx_gid_uid_gc key_len: 4 ref: const rows: 4 Extra: Using where; Using index 1 row in set (0.01 sec)
這時候的執行計畫的 Extra 資訊中已經沒有“Using index for group-by”了,但並不是說 MySQL 的 GROUP BY 操作並不是通過索引完成的,只不過是需要訪問 WHERE 條件所限定的所有索引鍵資訊之後才能得出結果。這就是通過緊湊索引掃描來實現 GROUP BY 的執行計畫輸出資訊。
下面這張圖片展示了大概的整個執行過程:
在 MySQL 中,MySQL Query Optimizer 首先會選擇嘗試通過鬆散索引掃描來實現 GROUP BY 操作,當發現某些情況無法滿足鬆散索引掃描實現 GROUP BY 的要求之後,才會嘗試通過緊湊索引掃描來實現。
當 GROUP BY 條件欄位並不連續或者不是索引首碼部分的時候,MySQL Query Optimizer 無法使用鬆散索引掃描,設定無法直接通過索引完成 GROUP BY 操作,因為缺失的索引鍵資訊無法得到。但是,如果 Query 語句中存在一個常量值來引用缺失的索引鍵,則可以使用緊湊索引掃描完成 GROUP BY 操作,因為常量填充了搜尋索引鍵中的“差距”,可以形成完整的索引首碼。這些索引首碼可以用於索引尋找。而如果需要排序GROUP BY結果,並且能夠形成索引首碼的搜尋索引鍵,MySQL還可以避免額外的排序操作,因為使用有順序的索引的首碼進行搜尋已經按順序檢索到了所有關鍵字。
3.使用暫存資料表實現 GROUP BY
MySQL 在進行 GROUP BY 操作的時候要想利用所有,必須滿足 GROUP BY 的欄位必須同時存放於同一個索引中,且該索引是一個有序索引(如 Hash 索引就不能滿足要求)。而且,並不只是如此,是否能夠利用索引來實現 GROUP BY 還與使用的彙總函式也有關係。
前面兩種 GROUP BY 的實現方式都是在有可以利用的索引的時候使用的,當 MySQL Query Optimizer 無法找到合適的索引可以利用的時候,就不得不先讀取需要的資料,然後通過暫存資料表來完成 GROUP BY 操作。
sky@localhost : example 09:02:40> EXPLAIN -> SELECT max(gmt_create) -> FROM group_message -> WHERE group_id > 1 and group_id < 10 -> GROUP BY user_id\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: group_message type: range possible_keys: idx_group_message_gid_uid,idx_gid_uid_gc key: idx_gid_uid_gc key_len: 4 ref: NULL rows: 32 Extra: Using where; Using index; Using temporary; Using filesort
這次的執行計畫非常明顯的告訴我們 MySQL 通過索引找到了我們需要的資料,然後建立了暫存資料表,又進行了排序操作,才得到我們需要的 GROUP BY 結果。整個執行過程大概如下圖所展示:
當 MySQL Query Optimizer 發現僅僅通過索引掃描並不能直接得到 GROUP BY 的結果之後,他就不得不選擇通過使用暫存資料表然後再排序的方式來實現 GROUP BY了。
在這樣樣本中即是這樣的情況。 group_id 並不是一個常量條件,而是一個範圍,而且 GROUP BY 欄位為 user_id。所以 MySQL 無法根據索引的順序來協助 GROUP BY 的實現,只能先通過索引範圍掃描得到需要的資料,然後將資料存入暫存資料表,然後再進行排序和分組操作來完成 GROUP BY。