首页 > 数据库 >MySQL——GROUP BY详解与优化

MySQL——GROUP BY详解与优化

时间:2023-07-22 21:48:08浏览次数:42  
标签:tmp GROUP 临时 age 详解 MySQL 排序

在 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通常用于以下场景:

  • 对数据进行分类和统计
  • 按特定条件对数据进行分组
  • 进行聚合操作,如计算总数、平均数、最大值、最小值等
  • 生成报表或汇总数据

标签:tmp,GROUP,临时,age,详解,MySQL,排序
From: https://www.cnblogs.com/xw-01/p/17574297.html

相关文章

  • mysql workbench中文设置
    MySQLWorkbench中文设置导言MySQLWorkbench是一款常用的数据库管理工具,支持多种语言。本文将教会你如何在MySQLWorkbench中设置中文环境。流程下面是整个过程的步骤,以表格形式展示:步骤操作步骤一在MySQLWorkbench中打开“Edit”菜单步骤二选择“Prefer......
  • mysql workbench linux
    在Linux系统中安装MySQLWorkbench的步骤MySQLWorkbench是一个强大的数据库设计和管理工具,可以在Linux系统上安装和使用。下面是安装MySQLWorkbench的步骤和相应的代码示例。步骤概述步骤描述步骤1更新软件包列表步骤2安装MySQLWorkbench依赖步骤3下载......
  • mysql update死锁
    如何实现“mysqlupdate死锁”引言MySQL的死锁是指两个或多个事务相互等待对方释放资源而无法继续执行的情况。在开发中,我们需要了解如何实现死锁,以便更好地理解死锁产生的原因,并能够针对性地解决这类问题。本文将介绍如何使用MySQL实现一个简单的update死锁场景,并提供相应的代码......
  • mysql update 返回主键
    MySQLUpdate返回主键当我们在使用MySQL进行数据更新操作时,有时候需要获取被更新的数据的主键。MySQL提供了一种方法可以在更新数据时返回主键值,这对于某些特定的业务场景非常有用。为什么需要返回主键值?在某些情况下,我们需要在更新数据之后获取该数据的主键值。例如,假设我们有......
  • mysql union 优化
    MySQLUnion优化1.概述在MySQL数据库中,UNION操作用于合并两个或多个SELECT语句的结果集。然而,当UNION操作涉及多个大表时,可能会导致性能下降。本文将介绍一些优化技巧,以提高UNION查询的性能。2.优化步骤步骤操作1使用UNIONALL替代UNION2添加索引......
  • mysql text 字段最大长度
    MySQLTEXT字段最大长度MySQL是一个关系型数据库管理系统,常用于存储和管理大量的数据。在MySQL中,TEXT字段是一种用于存储大块文本数据的数据类型。本文将介绍MySQLTEXT字段的最大长度限制,并提供相关的代码示例。1.什么是TEXT字段在MySQL中,TEXT字段是用于存储变长文本数据的数......
  • mysql sql语句 添加字段
    添加字段的MySQLSQL语句在MySQL中,可以使用SQL语句来添加字段到一个已存在的表中。添加字段可以改变表的结构,使其具有更多的列或属性,以适应新的需求。添加字段的语法添加字段的语法如下:ALTERTABLEtable_nameADDcolumn_namedatatype;其中,ALTERTABLE是用于修改表结构的关......
  • pom.xml中的<scope></scope>标签详解
       1、scope标签的值的种类:1、compile:默认值,可省略不写。此值表示该依赖需要参与到项目的编译、测试以及运行周期中,打包时也要包含进去。2、test:该依赖仅仅参与测试相关的工作,包括测试代码的编译和执行,不会被打包,例如:junit。3、runtime:该依赖项目无需参与项目的编译,不过后期的......
  • mysql8.0查询死锁语句
    如何实现“mysql8.0查询死锁语句”简介在开发过程中,我们经常会遇到数据库死锁的情况。死锁是指两个或多个事务互相持有对方所需要的资源,导致事务无法继续执行的情况。MySQL8.0提供了一种查看当前死锁情况的方法,以便我们能够定位和解决问题。本文将详细介绍如何在MySQL8.0中......
  • mysql8 wait_timeout durid
    如何设置MySQL8wait_timeoutdurid流程概述设置MySQL8的wait_timeoutdurid需要经过以下步骤:步骤操作1登录MySQL数据库2查看当前的wait_timeout和interactive_timeout3修改wait_timeout和interactive_timeout的值4保存修改并重启MySQL服务5确认修改......