一個標準的 Group by 語句包含排序、分組、聚合函數,好比 select a,count(*) from t group by a ; 這個語句默認使用 a 進行排序。若是 a 列沒有索引,那麼就會建立臨時表來統計 a和 count(*),而後再經過 sort_buffer 按 a 進行排序。算法
結構:數組
create table t1(id int primary key, a int, b int, index(a)); delimiter ;; create procedure idata() begin declare i int; set i=1; while(i<=1000)do insert into t1 values(i, i, i); set i=i+1; end while; end;; delimiter ; call idata();
函數就是向 t1 中插入1000條語句,從(1,1,1) 到(1000,1000,1000)。函數
執行 select id%10 as m, count(*) as c from t1 group by m;性能
解析:優化
Using index,表示這個語句使用了覆蓋索引,選擇了索引 a,不須要回表;
Using temporary,表示使用了臨時表;
Using filesort,表示須要排序。spa
過程:code
一、建立內存臨時表,表裏有兩個字段 m 和 c,主鍵是 m;
二、掃描表 t1 的索引 a,依次取出葉子節點上的 id 值,計算 id%10 的結果,記爲 x;
1)若是臨時表中沒有主鍵爲 x 的行,就插入一個記錄 (x,1);
2)若是表中有主鍵爲 x 的行,就將 x 這一行的 c 值加 1;blog
第2 步若是發現內存臨時表存儲的總字段長度到達參數 tmp_table_size 設置的大小,那麼就會將內存臨時表升級爲磁盤臨時表,而後從新開始遍歷計算。
三、遍歷完成後,再根據字段 m 作排序,獲得結果集返回給客戶端。排序
最後的排序就是下圖虛線框中的操做,若是 sort_buffer 設置的大小不夠大,那麼就會使用臨時表來輔助排序。索引
未優化(也就是分組列沒有索引)的 group by 的總過程能夠歸納爲:由於數據是無序的,因此須要建立臨時表,而後一個一個判斷屬於哪一個分組,最後再根據分組列進行排序。因此,優化能夠有兩個思路:
在明確返回的數據不須要排序的狀況下,能夠禁止排序,也就是將上面的語句改爲 select a,count(*) from t group by a order by null。
若是記錄都按照排序字段排序,那麼數據就變成了下面的結構:
這樣在實際獲取要返回的字段或計算聚合函數時,只須要按順序依次訪問,等到列值變成下一個就知道當前組訪問結束,將以前統計的數據直接返回。這樣就避免了建立臨時表,同時排序也不須要使用 sort_buffer 進行額外排序。這樣就極大地提升了執行的效率。
一、若是分組字段適合建立索引就直接爲分組字段建立索引。
MySQL 5.7 版本支持了 generated column 機制,用來實現列數據的關聯更新。你能夠用下面的方法建立一個列 z,而後在 z 列上建立一個索引(若是是 MySQL 5.6 及以前的版本,你也能夠建立普通列和索引,來解決這個問題)
alter table t1 add column z int generated always as(id % 100), add index(z);
而後解析:
這時沒有用到臨時表和額外排序,因此性能提高。
二、若是分組字段不適合(使用率很低),那麼可使用 SQL_BIG_RESULT 來嘗試優化。
在 group by 語句中加入 SQL_BIG_RESULT 這個提示(hint),就能夠告訴優化器:這個語句涉及的數據量很大,請直接用磁盤臨時表。MySQL 的優化器一看,磁盤臨時表是 B+ 樹存儲,存儲效率不如數組來得高。因此,既然使用SQL_BIG_RESULT來講明數據量很大,那從磁盤空間考慮,仍是直接用數組來存吧。因此在使用 SQL_BIG_RESULT 後優化器會使用數組結構的磁盤臨時表。
可是若是在未達到磁盤臨時表的使用條件是不會使用磁盤臨時表的,也就是在 sort_buffer 空間可以存儲要返回和排序的總字段長度時,就使用數組結構的 sort_buffer ,若是總字段超過 sort_buffer 大小,那麼就再加上數組結構的磁盤臨時表來幫助排序。
那麼在 sort_buffer 空間足夠的狀況下, sort_buffer 內部就會對數據進行排序,這樣也就起到了索引的做用,
仍是以上面的例子來看,使用 SQL_BIG_RESULT
alter table t1 add column z int generated always as(id % 100), add index(z);
具體過程以下:
一、初始化 sort_buffer,肯定放入一個整型字段,記爲 m;
二、掃描表 t1 的索引 a,依次取出裏面的 id 值, 將 id%10 的值存入 sort_buffer 中;
三、掃描完成後,對 sort_buffer 的字段 m 作排序(若是 sort_buffer 內存不夠用,就會利用磁盤臨時文件輔助排序);
四、排序完成後,就獲得了一個有序數組。
解析:
能夠看到此時就沒有使用臨時表了,而是直接使用 sort_buffer 進行排序,這樣就省去了使用臨時錶帶來的性能消耗。
一、若是對 group by 語句的結果沒有排序要求,要在語句後面加 order by null;那麼通常狀況就不須要使用臨時表了(上面兩個優化都是在要求排序的前提下提出的優化方式)
二、儘可能讓 group by 過程用上表的索引,確認方法是 explain 結果裏沒有 Using temporary 和 Using filesort;
三、若是 group by 須要統計的數據量不大,儘可能只使用內存臨時表;也能夠經過適當調大 tmp_table_size 參數,來避免用到磁盤臨時表;
四、若是數據量實在太大,使用 SQL_BIG_RESULT 這個提示,來告訴優化器直接使用排序算法獲得 group by 的結果。