日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

阿里二面:group by 怎么优化?

發布時間:2025/3/15 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 阿里二面: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='商品訂單表';

數據如下,我之前就導入準備好的:

數據準備好了,需求也來了?,F在產品要求統計表中每個城市的下單人數,這個需求是不是很簡單?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 怎么优化?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。