阿里二面:group by 怎么优化?
01 前言
哈嘍,我是狗哥,好久不見呀!是的,我又又換了工作。最近一直在面試這幾天剛好整理下在面試中被問到有意思的問題,也借此機會跟大家分享下。
這家企業的面試官有點意思,一面是個同齡小哥,一起聊了兩個小時(聊到我嘴都干了)。二面是個從阿里出來的架構師,視頻面試,我做完自我介紹之后,他一開場就問我:
對 MySQL 熟悉嗎?
我一愣,隨之意識到這是個坑。他肯定想問我某方面的原理了,恰好我研究過索引。就回答:
對索引比較熟悉。
他:
group by 是怎么實現分組的?
還好我又復習,基本上 group by 用法、工作原理、怎么優化之類的都答到點子上。今天也跟大家盤一盤 group by,我將從原理講到最終優化,給大家聊聊 group by,希望對你有所幫助。
國際慣例,先上思維導圖。
02 一個簡單的例子
還是借我們之前講 order by 時創建的商品訂單表來演示。建表語句:
CREATE?TABLE?`sale_order`?(`id`?bigint(20)?NOT?NULL?AUTO_INCREMENT?COMMENT?'主鍵',`user_code`?varchar(64)?NOT?NULL?COMMENT?'用戶編號',`goods_name`?varchar(64)?NOT?NULL?COMMENT?'商品名稱',`order_date`?timestamp?NULL?DEFAULT?CURRENT_TIMESTAMP?COMMENT?'下單時間',`city`?varchar(64)?DEFAULT?NULL?COMMENT?'下單城市',`order_num`?int(10)?NOT?NULL?COMMENT?'訂單數量',PRIMARY?KEY?(`id`)?USING?BTREE )?ENGINE=InnoDB?AUTO_INCREMENT=10001?DEFAULT?CHARSET=utf8?ROW_FORMAT=COMPACT?COMMENT='商品訂單表';數據如下,我之前就導入準備好的:
數據準備好了,需求也來了。現在產品要求統計表中每個城市的下單人數,這個需求是不是很簡單?sql 語句我們也可以很快給出:
select?city,?count(*)?as?num?from?sale_order?group?by?city;這條 sql 的結果也很快就出來了:
sql 我們會寫,結果也很快就出來了。但是原理你知道么?執行流程又是怎樣的呢?
03 group by 的原理
3.1 explain 分析
廢話不多說,遇事不決 explain。想要知道 sql 的性能咋樣,怎么執行的,都要用 explain 分析。想要知道 explain 的每個指標代表啥意思,可以看我之前的文章:《explain 很重要嗎?》
注意到最后一列 Extra ,這列代表的是?sql 執行過程中會做什么?上圖中這列有兩個值,一個是 Using temporary,一個是 Using filesort。
Using temporary:代表需要用到臨時表。OS:這是個啥???
Using filesort:需要排序。OS:挖草,還需要排序???
要想搞明白為什么需要臨時表和排序,我們就得分析 group by 的執行流程了。
3.2 執行流程
根據 explain 分析,我們知道執行過程中肯定有創建臨時表和排序兩個步驟,下面來分析一下:
創建內存臨時表,表里面有兩個字段:city 和 num;
全表掃描 sale_order 表,取出 city = 某城市(比如廣州、深圳、上海,囊括你表里涉及到的城市)的記錄
臨時表沒有 city = 某城市的記錄,直接插入,并記為 (某城市,1);
臨時表里有 city = 某城市的記錄,直接更新,把 num 值 +1
重復步驟 2 直至遍歷完成,根據 city 字段做排序,然后把結果集返回客戶端。
至此整個過程就完事了。我知道這樣不直觀,所以我又畫個圖,方便你們理解:
圖中最后一步,對內存臨時表的排序,具體的細節在之前的?《order by 是怎么排序的?》一文章中已經有過介紹,歡迎點擊跳轉。同樣是非常細節的一個 mysql 關鍵字,強烈推薦你去看下。
04 group by 中使用 where & having
寫到這里,有小伙伴就說了。狗哥你這里描述的只是 group by 的單獨執行過程,很簡單呀。我也會,如果加上 where 或者 having 或者兩者都加上的時候的執行過程是怎樣的呢?
4.1 group by + where
現在產品又改需求統計每個城市下的下單人數,且下的訂單量要大于 2。OS:mmp,又改
按照慣例,看到 where 我們一般想到怎么優化?沒錯,加索引嘛。
加索引:
alter?table?sale_order?add?index?idx_order_num?(order_num);最終語句:
select?city,?count(*)?as?num?from?sale_order?where?order_num?>?2?group?by?city;結果:
explain 分析:
從上圖得知,加上索引之后。這條語句命中了索引 idx_order_number,并且此時的 Extra 多了 Using index Condition 的執行計劃。type 變成了 range 說明不用全表掃描。
解釋下 Using index Condition:會先條件過濾索引,過濾完索引后找到所有符合索引條件的數據行,常見于 where 中有 between > < 等條件的 sql 語句。
它的出現說明這個語句先走索引過濾掉不符合 where 條件的數據,再去統計,然后排序,最后返回客戶端。流程如下:
創建內存臨時表,表里面有兩個字段:city 和 num;
根據索引 idx_order_num 找到大于 2 的數據的主鍵 ID;
通過主鍵 ID 取出 city = 某城市(比如廣州、深圳、上海,囊括你表里涉及到的城市)的記錄;
臨時表沒有 city = 某城市的記錄,直接插入,并記為 (某城市,1);
臨時表里有 city = 某城市的記錄,直接更新,把 num 值 +1。
重復 2、3 步驟,直至找到所有嗎,滿足 order_num > 2 的記錄。根據 city 字段做排序,然后把結果集返回客戶端。
PS:回表的概念我就不說了哈,有興趣的可以看我之前的《MySQL 索引詳解》文章,強烈建議你去看,非常重要的是概念。
4.2 group by + having
現在產品又改需求統計每個城市的下單的人數,且總的下單人數需要在 100 以上。OS:mmp,又改
根據需求很快寫出 sql 語句:
select?city,?count(*)?as?num?from?sale_order?group?by?city?having?num?>?100;再用 explain 分析一下,得出如下結果:
哇草,咋回事?跟沒加 having 的執行流程一樣的?你沒看錯,其實 having 不直接參與到執行計劃中去,它是對結果集操作的,所以這里的加的 having 跟沒加是一樣的執行計劃。畫個圖,大概就是這樣的:
4.3 group by + where + having
現在產品又改需求統計每個城市的下單超過兩單的人數,且總的人數需要在 100 以上。OS:mmmp,又改
按照慣例,我們給 where 條件加上索引:
alter?table?sale_order?add?index?idx_order_num?(order_num);根據需求很快寫出 sql 語句:
select?city,?count(*)?as?num?from?sale_order?where?order_nunm?>?2?group?by?city?having?num?>?100;explain 結果:
執行流程:
創建內存臨時表,表里面有兩個字段:city 和 num;
根據索引 idx_order_num 找到大于 2 的數據的主鍵 ID;
通過主鍵 ID 取出 city = 某城市(比如廣州、深圳、上海,囊括你表里涉及到的城市)的記錄;
臨時表沒有 city = 某城市的記錄,直接插入,并記為 (某城市,1);
臨時表里有 city = 某城市的記錄,直接更新,把 num 值 +1。
重復 2、3 步驟,直至找到所有嗎,滿足 order_num > 2 的記錄。根據 city 字段做排序。
having 對結果集進行過濾,并返回客戶端
不難看出這里的執行流程跟 4.1 一樣就多了個 having 過濾
05 group by 優化
根據上面的分析,我們知道 group by 是需要創建臨時表并且排序的。耗時也應該在這兩個步驟,那我們應該從這兩個步驟入手優化。
如果分組字段本身就是有序的,我們是不是就不用排序了?或者我們的需求并沒有要求排序是不是就可以優化了?如果必須使用臨時表,我們是不是可以只用內存臨時表呢?如果數據量實在是太大,是不是可以直接用磁盤臨時表,而不是發現內存臨時表不夠大才用它呢?
以上可以總結出四個優化方案:
分組字段加索引
order by null 不排序
盡量使用內存臨時表
SQL_BIG_RESULT
5.1 分組字段加索引
select?city,?count(*)?as?num?from?sale_order?group?by?city;上面的 sql 中,city 沒加索引,所以這時的 group by 還是要使用臨時表的。那我們可不可以個組合索引 idx_city,結果如下所示:
加索引:
alter?table?sale_order?add?index?idx_city?(city);結果:
Extra 是不是 Using temporary 和 Using filesort 都沒了?所以不用排序也不用臨時表啦。那有小伙伴又問了,那我有 where 條件怎么辦?那就加組合索引唄:
alter?table?sale_order?add?index?idx_order_num_city(order_num,city);但是這種情況只適用于 where 條件是等值的,如果有大于、小于的情況還是避免不了排序和使用臨時表。適用情況:
select?city,?count(*)?as?num?from?sale_order?where?order_num?=?2?group?by?city;不適用情況:
select?city,?count(*)?as?num?from?sale_order?where?order_num?>?2?group?by?city;5.2 order by null 避免排序
如果需求是不用排序,我們就可以這樣做。在 sql 末尾加上 order by null
select?city,?count(*)?as?num?from?sale_order?where?order_num?>?2?group?by?city?order?by?null;從分析結果看,還是需要使用臨時表的。
5.3 盡量使用內存臨時表
有些小伙伴可能很懵哈,內存臨時表是啥?其實 mysql 臨時表分內存臨時表和磁盤臨時表。但是這里就不展開了,有時間專門寫一篇文章介紹。
group by 在執行過程中使用內存臨時表還是不夠用,那就會使用磁盤臨時表。內存臨時表的大小是有限制的,mysql 中 tmp_table_size 代表的就是內存臨時表的大小,默認是 16M。當然你可以自定義社會中適當大一點,這就要根據實際情況來定了。
比如:可以設置成 32M,也就是 33554432 字節。
set?tmp_table_size=33554432;5.4 SQL_BIG_RESULT
如果數據量實在過大,大到內存臨時表都不夠用了,這時就轉向使用磁盤臨時表。而發現不夠用再轉向這個過程也是很耗時的,那我們有沒有一種方法,可以告訴 mysql 從一開始就使用 磁盤臨時表呢?
有的,在 group by 語句中加入 SQL_BIG_RESULT 提示 MySQL 優化器直接用磁盤臨時表。優化器分析,磁盤臨時表是 B+ 樹存儲,存儲效率不如數組來得高。所以直接用數組存儲。用法如下:
select?SQL_BIG_RESULT?city,?count(*)?as?num?from?sale_order?where?group?by?city;此時的執行過程就不需要創建臨時表啦:
初始化 sort_buffer(排序緩沖區),放入 city 字段;
掃描 sale_order 表,取出 city 的值存入 sort_buffer 中;
掃描完成后,對 sort_buffer 的字段 city 做排序(如果 sort_buffer 內存不夠用,就會利用磁盤臨時文件輔助排序);
排序完成后,就得到了一個有序數組。
根據有序數組,得到數組里面的不同值,以及每個值的出現次數
06 group by 面試題
6.1 group by 一定要配合聚合函數使用嗎?
不一定,以下 sql 語句,我用的 MySQL 5.7.13 運行是報錯的;但是我司的 MySQL 8.0 版本是沒有問題的。
select?goods_name,?city?from?sale_order?group?by?city;出現這個錯誤的原因是 mysql 的 sql_mode 開啟了 ONLY_FULL_GROUP_BY 模式。查看 sql_mode:
select?@@GLOBAL.sql_mode;如果想要不做限制的話,直接重新設置 sql_mode 的值,把 ONLY_FULL_GROUP_BY 去掉即可。當然,開啟這個要慎重,有可能會造成一些意想不到的錯誤,一般情況下還是加上這個設置比較穩妥。
6.2 group by 后面的一定要出現在 select 中嗎?
不一定,我的就沒報錯。當然,這個還跟版本有關系。大家可以回去自己實踐下。
select?max(order_num)?from?sale_order?group?by?city;6.1 where & having 的區別?
where 用于條件篩選,having 用于分組后篩選
where 條件后面不能跟聚合函數,having 一般配合 group by 或者聚合函數(min、max、avg、count、sum)使用
where 用在 group by 之前,having 用在 group by 之后
07 參考鏈接
https://time.geekbang.org/column/article/80477?cid=100020801
https://www.cnblogs.com/perfei/p/14677933.html
https://cloud.tencent.com/developer/article/1941787
https://blog.csdn.net/ryan007liu/article/details/91441479
https://www.cnblogs.com/muhy/p/10558849.html
08?總結
本文我們聊了 group by 的基本和進階用法,還用 explain 分析了不同 group by 的執行流程;從上面的分析中知道了 group by 的性能瓶頸是使用臨時表和排序,從這兩個方面提出了分組字段加索引、order by null、盡量使用內存臨時表以及使用 SQL_BIG_RESULT 優化等 4 個優化方案,最后還聊了下 group by 常見的面試題。
完
往期推薦
巨坑!這公司的行為,挺適合清明節!
美國國家安全局是如何入侵你的電腦的?
我滴個乖乖,我復現了Spring的漏洞,害怕!
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結
以上是生活随笔為你收集整理的阿里二面:group by 怎么优化?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java中this_夯实Java基础系列
- 下一篇: 互联网寒冬来袭,有一家公司却逆流而上!