MySQL——GROUP BY詳解與優化

来源:https://www.cnblogs.com/xw-01/archive/2023/07/22/17574297.html
-Advertisement-
Play Games

> 在 MySQL 中,GROUP BY用於將具有指定列中相同值的行分組在一起。這是在處理大量數據時非常有用的功能,允許對數據進行分類和聚合。 # 基本使用 ## 語法 以下是GROUP BY子句的基本語法: """ SELECT col1, col2, ..., aggregate_functio ...


在 MySQL 中,GROUP BY用於將具有指定列中相同值的行分組在一起。這是在處理大量數據時非常有用的功能,允許對數據進行分類和聚合。

基本使用

語法

以下是GROUP BY子句的基本語法:
"""

SELECT col1, col2, ..., aggregate_function(col_name)
FROM table_name
WHERE condition
GROUP BY col1, col2, ...;

"""
其中,col1, col2, ...是要分組的列名,aggregate_function是用於聚合數據的函數,如SUM,

AVG, MAX, MIN等。table_name是要從中檢索數據的表的名稱,condition是可選的查詢條

件。

示例

"""

SELECT column1, column2, COUNT(*)
FROM table_name
WHERE condition
GROUP BY column1, column2
ORDER BY column1, column2;

"""
在這個示例中,選擇了column1和column2兩列,並對它們進行了分組。使用COUNT(*)函

數來計算每個組中的行數。使用ORDER BY子句按column1和column2升序排序結果集。

那怎麼查詢非分組的列名呢?

一般來講 SELECT 中的值要麼是來自於聚合函數(sum、avg、max等)的結果,要麼是來自

於 group by 後面的列。

從MySQL 5.7.5之前預設是支持的,之後的版本預設SQL模式包括ONLY_FULL_GROUP_BY,
"""

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.18    |
+-----------+
1 row in set (0.06 sec)
mysql> select @@global.sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@global.sql_mode                                                                                                     |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.18 sec)

"""
在這種模式下執行 SQL 會報下麵的錯誤


"""

mysql> select * from user group by age;
1055 - Expression #1 of SELECT list is not in GROUP BY clause 
and contains nonaggregated column xxx which is not functionally
dependent on columns in GROUP BY clause; 
this is incompatible with sql_mode=only_full_group_by

"""


可以通過下麵兩種方式解決:

  1. 重新設置 sql_mode,去掉ONLY_FULL_GROUP_BY即可

  2. 使用 any_value() 或 group_concat()

  • any_value():將分到同一組的數據里第一條數據的指定列值作為返回數據

  • group_concat():將分到同一組的數據預設用逗號隔開作為返回數據


"""

mysql> select age, any_value(id) from `user` GROUP BY age;
+-----+---------------+
| age | any_value(id) |
+-----+---------------+
|   3 |             0 |
|   6 |             3 |
|   7 |             5 |
|  12 |             1 |
|  14 |             2 |
|  19 |             7 |
+-----+---------------+
6 rows in set (0.15 sec)

mysql> select age, group_concat(id) from `user` GROUP BY age;
+-----+------------------+
| age | group_concat(id) |
+-----+------------------+
|   3 | 0,4              |
|   6 | 3                |
|   7 | 5                |
|  12 | 1                |
|  14 | 2                |
|  19 | 7                |
+-----+------------------+
6 rows in set (0.05 sec)

"""


不同版本的排序

我們以下麵這個user表為例,看下在不同版本下有什麼區別?

"""

mysql>  show create table  user;
+-------+---------------------------------+
CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL ,
  `age` int(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `nameIndex` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 
+-------+---------------------------------+
mysql> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
|  0 | 陳   |   3 |
|  1 | 李   |  12 |
|  2 | 張   |  14 |
|  3 | 陳   |   6 |
|  4 | 李   |   3 |
|  5 | NULL |   7 |
|  7 | 張   |  19 |
+----+------+-----+
7 rows in set (0.06 sec)

"""


在MySQL 5.7中


image


在MySQL 8.0中


image


同樣的SQL在MySQL 5.7中與MySQL 8.0中執行結果是不一樣的,在MySQL 5.7中數據預設

按照分組列升序展示,在MySQL 8.0中則沒有排序,所以在MySQL 5.7中執行計劃裡面的

Extra 這個欄位的多了一個 Using filesort。

因為在MySQL 5.7中,GROUP BY 預設隱式排序,按GROUP BY列按升序排序。如果不想在

執行 GROUP BY 時執行排序的開銷,可以禁用排序:

"""

GROUP BY column_name ORDER BY NULL

"""
然而,在MySQL 8.0中,GROUP BY預設不會使用排序功能,除非使用了ORDER BY語句。

工作原理

執行流程

我們先來看下下麵這條sql語句在MySQL 5.7中的執行計劃:
"""

  explain select age,count(age) from user where name ='李'  GROUP BY age;

"""
image


在Extra欄位裡面, 我們可以看到三個信息:

  • Using index condition: 表示這個語句使用了索引來過濾;
  • Using temporary: 表示使用了臨時表;
  • Using filesort: 表示需要排序

這個語句的執行流程是這樣的:

  1. 創建一個臨時表。表裡有兩個欄位 age 和 count(age)、主鍵為 age
  2. 掃描普通索引nameIndex ,找到 name ='李' 主鍵 ID;
  3. 通過主鍵ID,回表找到 age=12 欄位值
  4. 判斷臨時表中有沒有主鍵為 12 的行
  • 沒有就插入一個記錄(12,1)
  • 就將12這一行的count(age)值加1

遍歷完成後, 需要根據欄位 age 做排序

  1. 初始化sort_buffer, sort_buffer中有兩個欄位

  2. 從記憶體臨時表中一行一行地取出數據,分別存入sort_buffer中的兩個欄位里。 這個過程要對記憶體臨時表做全表掃描。

  3. 在sort_buffer中根據age的值進行排序。

  4. 排序完成後,返回給客戶端。

記憶體臨時表排序的時候使用了rowid排序方法。


"""

"filesort_summary":{  
	"rows":2,  
	"examined_rows":2,  
	"number_of_tmp_files":0,  
	"sort_buffer_size":320,  
	"sort_mode":"<sort_key, rowid>"  
}

"""


臨時表

記憶體臨時表

由於本例子只有幾行數據, 記憶體可以放得下,因此只使用了記憶體臨時表。 但是記憶體臨時表的

大小是有限制的, 參數 tmp_table_size 表示臨時表記憶體大小, 預設是16M。記憶體臨時表使

用的是memory引擎。

"""

mysql> show  variables like '%tmp_table_size%';
+----------------+---------+
| Variable_name  | Value   |
+----------------+---------+
| tmp_table_size | 2097152 |
+----------------+---------+
1 row in set (0.04 sec)

"""

磁碟臨時表

如果臨時表大小超過了tmp_table_size, 那麼記憶體臨時表就會轉成磁碟臨時表。磁碟臨時表

使用的引擎預設是InnoDB, 是由參數internal_tmp_disk_storage_engine 控制

"""

mysql> show variables like '%internal_tmp_disk_storage_engine%';
+----------------------------------+--------+
| Variable_name                    | Value  |
+----------------------------------+--------+
| internal_tmp_disk_storage_engine | InnoDB |
+----------------------------------+--------+
1 row in set (0.04 sec)

"""


為了復現生成磁碟臨時表,把 tmp_table_size設置小一點,通過查

Created_tmp_disk_tables值,查看對應的磁碟臨時表數量

"""

mysql> set tmp_table_size=1;
select age,count(age) from user where name ='李'  GROUP BY age ORDER BY age ;
show status like '%Created_tmp%';
Query OK, 0 rows affected (0.02 sec)

+-----+------------+
| age | count(age) |
+-----+------------+
|   3 |          1 |
|  12 |          1 |
+-----+------------+
2 rows in set (0.03 sec)

+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 3     |
| Created_tmp_files       | 60    |
| Created_tmp_tables      | 6     |
+-------------------------+-------+
3 rows in set (0.04 sec)

"""


Created_tmp_tables:在記憶體中創建內部臨時表時或在磁碟,伺服器將遞增此值。

Created_tmp_disk_tables:在磁碟上創建內部臨時表時, 伺服器遞增此值

一般理想的配置是:

"""

Created_tmp_disk_tables / Created_tmp_tables * 100% <= 25% 

"""

分組優化

不論是使用記憶體臨時表還是磁碟臨時表,group by需要構造一個帶唯一索引的表, 執行代價

都是比較高的。如果表的數據量比較大,執行起來就會很慢。

使用索引

如果可以確保輸入的數據是有序的,那麼 group by的時候, 就只需要從左到右,順序掃描,

依次累加。那就是InnoDB的索引,對索引列分組不需要臨時表,也不需要排序。

image


增大tmp_table_size

如果group by需要統計的數據量不大, 儘量只使用記憶體臨時表; 可以通過適當調大

tmp_table_size參數, 來避免用到磁碟臨時表。

使用SQL_BIG_RESULT

如果一個group by語句中需要放到臨時表上的數據量特別大,還是按照上面的邏輯,先放到

記憶體臨時表,插入一部分數據後,發現記憶體臨時表不夠用了再轉成磁碟臨時表,那感覺就沒必

要了,那怎麼樣可以直接使用磁碟臨時表呢?

使用SQL_BIG_RESULT, 可以與 SELECT 語句中的GROUP BY或DISTINCT關鍵字一起使用。

它的作用是告訴MySQL優化器,查詢結果集較大,直接用磁碟臨時表。MySQL會使用基於磁

盤的臨時表進行排序

例如,以下是一個使用SQL_BIG_RESULT的示例:
"""

SELECT SQL_BIG_RESULT col1, col2
FROM my_table
GROUP BY col1;

"""
需要註意的是,使用SQL_BIG_RESULT會增加伺服器的記憶體和CPU使用量,因此應該仔細評

估是否需要使用它。通常情況下,只有在處理大型數據集時才需要使用。

禁用排序

在MySQL 5.7中,如果對group by語句的結果沒有排序要求,在語句後面加 order by null,

禁用排序,減少不必要的排序開銷。

GROUP BY 和 DISTINCT 的區別

首先是使用方式不同:雖然在某些情況下 DISTINCT 和 GROUP BY 可以實現相同的結果,但

通常情況下,它們用於不同的目的,一個是去重,一個是聚合。

  • DISTINCT 關鍵字用於返回 SELECT 查詢中不同的值,即去重。它會掃描所有的行並去除重覆的行。

  • GROUP BY 關鍵字用於將結果集按照指定列進行分組,並對每個分組執行聚合函數。

再就是在性能上:如果在不需要執行聚合函數時,DISTINCT 和GROUP BY這兩條語句的語義

和執行流程是相同的,因此執行性能也相同


image


使用場景

GROUP BY通常用於以下場景:

  • 對數據進行分類和統計
  • 按特定條件對數據進行分組
  • 進行聚合操作,如計算總數、平均數、最大值、最小值等
  • 生成報表或彙總數據

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # .Net 一套介面多實現 介面(`interface`)可理解為規範、標準、協議。介面是用來約束各方都在同一組規範下工作。 電腦外設USB介面,各個品牌商家生產的U盤、滑鼠都能夠被電腦主板識別並工作,這是因為個生產商都遵循實現了USB介面協議。 在編程中`介面`應用非常廣泛,例如`IDbConn ...
  • 基礎操作 CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.Token.Register(() => { Console.WriteLine ...
  • ## 一:背景 ### 1. 講故事 在 dump 分析旅程中,經常會遇到很多朋友反饋一類問題,比如: * 方法平時都執行的特別快,但有時候會特別慢,怎麼排查? * 我的方法第一次執行特別慢,能看到慢在哪裡嗎? 相信有朋友肯定說,加些日誌不就好了,大方向肯定是沒問題的,但加日誌的顆粒度會比較粗而且侵 ...
  • **前言** 小編在油管學習.NET知識,刷到一個外國小哥在用一個類似Idea的IDE開發.NET。出於好奇,小編在某谷搜索Idea支持.NET嗎?結果當然不支持,Idea主要是開發Java的IDE。幸好搜索引擎有個圖片搜索功能,於是拿圖片搜索了這個IDE,原來它的名字叫Rider,是Idea同一家 ...
  • # Unity UGUI的VerticalLayoutGroup(垂直佈局)組件的介紹及使用 ## 1. 什麼是VerticalLayoutGroup組件? VerticalLayoutGroup是Unity UGUI中的一種佈局組件,用於在垂直方向上自動排列子對象。它可以根據子對象的大小和佈局設置 ...
  • ## 引言 彙總一下所有的 .NET IL 指令,以及它們的名稱、操作碼值、堆棧轉換行為和描述。 作為反編譯IL代碼時的查詢字典。 ## IL 指令集列表 以下內容來自[微軟官方文檔](https://learn.microsoft.com/en-us/dotnet/api/system.refle ...
  • ### 一、文件、目錄許可權和所有者 簡介:用戶對一個文件或目錄具有訪問許可權,這些訪問許可權決定了誰能訪問,以及如何範圍這些文件和目錄。通過設置許可權可以限制或允許以下三種用戶訪問: - 文件的用戶所有者(屬主) - 文件的組群所有者(用戶所在組的同組用戶) - 系統中的其他用戶 在linux系統中,每一 ...
  • > ## ESP32連接MQ Sensor實現氣味反應 > > > ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/1961512734a74deb9d455b59cb1ccdab.png#pic_center) > > > | [ESP32+MQTT+MySQL實 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...