ToB企服应用市场:ToB评测及商务社交产业平台

标题: MySQL——GROUP BY详解与优化 [打印本页]

作者: 灌篮少年    时间: 2023-7-23 00:12
标题: MySQL——GROUP BY详解与优化
在 MySQL 中,GROUP BY用于将具有指定列中相同值的行分组在一起。这是在处理大量数据时非常有用的功能,允许对数据进行分类和聚合。
基本使用

语法

以下是GROUP BY子句的基本语法:
"""
  1. SELECT col1, col2, ..., aggregate_function(col_name)
  2. FROM table_name
  3. WHERE condition
  4. GROUP BY col1, col2, ...;
复制代码
"""
其中,col1, col2, ...是要分组的列名,aggregate_function是用于聚合数据的函数,如SUM,
AVG, MAX, MIN等。table_name是要从中检索数据的表的名称,condition是可选的查询条
件。
示例

"""
  1. SELECT column1, column2, COUNT(*)
  2. FROM table_name
  3. WHERE condition
  4. GROUP BY column1, column2
  5. 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,
"""
  1. mysql> select version();
  2. +-----------+
  3. | version() |
  4. +-----------+
  5. | 8.0.18    |
  6. +-----------+
  7. 1 row in set (0.06 sec)
  8. mysql> select @@global.sql_mode;
  9. +-----------------------------------------------------------------------------------------------------------------------+
  10. | @@global.sql_mode                                                                                                     |
  11. +-----------------------------------------------------------------------------------------------------------------------+
  12. | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,
  13. +-----------------------------------------------------------------------------------------------------------------------+
  14. 1 row in set (0.18 sec)
复制代码
"""
在这种模式下执行 SQL 会报下面的错误
"""
  1. mysql> select * from user group by age;
  2. 1055 - Expression #1 of SELECT list is not in GROUP BY clause
  3. and contains nonaggregated column xxx which is not functionally
  4. dependent on columns in GROUP BY clause;
  5. this is incompatible with sql_mode=only_full_group_by
复制代码
"""
可以通过下面两种方式解决:

"""
  1. mysql> select age, any_value(id) from `user` GROUP BY age;
  2. +-----+---------------+
  3. | age | any_value(id) |
  4. +-----+---------------+
  5. |   3 |             0 |
  6. |   6 |             3 |
  7. |   7 |             5 |
  8. |  12 |             1 |
  9. |  14 |             2 |
  10. |  19 |             7 |
  11. +-----+---------------+
  12. 6 rows in set (0.15 sec)
  13. mysql> select age, group_concat(id) from `user` GROUP BY age;
  14. +-----+------------------+
  15. | age | group_concat(id) |
  16. +-----+------------------+
  17. |   3 | 0,4              |
  18. |   6 | 3                |
  19. |   7 | 5                |
  20. |  12 | 1                |
  21. |  14 | 2                |
  22. |  19 | 7                |
  23. +-----+------------------+
  24. 6 rows in set (0.05 sec)
复制代码
"""
不同版本的排序

我们以下面这个user表为例,看下在不同版本下有什么区别?
"""
  1. mysql>  show create table  user;
  2. +-------+---------------------------------+
  3. CREATE TABLE `user` (
  4.   `id` int(11) NOT NULL,
  5.   `name` varchar(255) DEFAULT NULL ,
  6.   `age` int(255) DEFAULT NULL,
  7.   PRIMARY KEY (`id`),
  8.   KEY `nameIndex` (`name`) USING BTREE
  9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8
  10. +-------+---------------------------------+
  11. mysql> select * from user;
  12. +----+------+-----+
  13. | id | name | age |
  14. +----+------+-----+
  15. |  0 | 陈   |   3 |
  16. |  1 | 李   |  12 |
  17. |  2 | 张   |  14 |
  18. |  3 | 陈   |   6 |
  19. |  4 | 李   |   3 |
  20. |  5 | NULL |   7 |
  21. |  7 | 张   |  19 |
  22. +----+------+-----+
  23. 7 rows in set (0.06 sec)
复制代码
"""
在MySQL 5.7中

在MySQL 8.0中

同样的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 时执行排序的开销,可以禁用排序:
"""
  1. GROUP BY column_name ORDER BY NULL
复制代码
"""
然而,在MySQL 8.0中,GROUP BY默认不会使用排序功能,除非使用了ORDER BY语句。
工作原理

执行流程

我们先来看下下面这条sql语句在MySQL 5.7中的执行计划:
"""
  1.   explain select age,count(age) from user where name ='李'  GROUP BY age;
复制代码
"""

在Extra字段里面, 我们可以看到三个信息:
这个语句的执行流程是这样的:

遍历完成后, 需要根据字段 age 做排序
内存临时表排序的时候使用了rowid排序方法。
"""
  1. "filesort_summary":{  
  2.         "rows":2,  
  3.         "examined_rows":2,  
  4.         "number_of_tmp_files":0,  
  5.         "sort_buffer_size":320,  
  6.         "sort_mode":"<sort_key, rowid>"  
  7. }
复制代码
"""
临时表

内存临时表

由于本例子只有几行数据, 内存可以放得下,因此只使用了内存临时表。 但是内存临时表的
大小是有限制的, 参数 tmp_table_size 表示临时表内存大小, 默认是16M。内存临时表使
用的是memory引擎。
"""
  1. mysql> show  variables like '%tmp_table_size%';
  2. +----------------+---------+
  3. | Variable_name  | Value   |
  4. +----------------+---------+
  5. | tmp_table_size | 2097152 |
  6. +----------------+---------+
  7. 1 row in set (0.04 sec)
复制代码
"""
磁盘临时表

如果临时表大小超过了tmp_table_size, 那么内存临时表就会转成磁盘临时表。磁盘临时表
使用的引擎默认是InnoDB, 是由参数internal_tmp_disk_storage_engine 控制
"""
  1. mysql> show variables like '%internal_tmp_disk_storage_engine%';
  2. +----------------------------------+--------+
  3. | Variable_name                    | Value  |
  4. +----------------------------------+--------+
  5. | internal_tmp_disk_storage_engine | InnoDB |
  6. +----------------------------------+--------+
  7. 1 row in set (0.04 sec)
复制代码
"""
为了复现生成磁盘临时表,把 tmp_table_size设置小一点,通过查
Created_tmp_disk_tables值,查看对应的磁盘临时表数量
"""
  1. mysql> set tmp_table_size=1;
  2. select age,count(age) from user where name ='李'  GROUP BY age ORDER BY age ;
  3. show status like '%Created_tmp%';
  4. Query OK, 0 rows affected (0.02 sec)
  5. +-----+------------+
  6. | age | count(age) |
  7. +-----+------------+
  8. |   3 |          1 |
  9. |  12 |          1 |
  10. +-----+------------+
  11. 2 rows in set (0.03 sec)
  12. +-------------------------+-------+
  13. | Variable_name           | Value |
  14. +-------------------------+-------+
  15. | Created_tmp_disk_tables | 3     |
  16. | Created_tmp_files       | 60    |
  17. | Created_tmp_tables      | 6     |
  18. +-------------------------+-------+
  19. 3 rows in set (0.04 sec)
复制代码
"""
Created_tmp_tables:在内存中创建内部临时表时或在磁盘,服务器将递增此值。
Created_tmp_disk_tables:在磁盘上创建内部临时表时, 服务器递增此值
一般理想的配置是:
"""
[code]Created_tmp_disk_tables / Created_tmp_tables * 100%




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4