MySQL group by语句如何优化

在MySQL中,新建立一张表,该表有三个字段,分别是id,a,b,插入1000条每个字段都相等的记录,如下:

mysql> show create table t1\G
*************************** 1. row ***************************
    Table: t1
Create Table: CREATE TABLE `t1` (
 `id` int(11) NOT NULL,
 `a` int(11) DEFAULT NULL,
 `b` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> select * from t1 limit 10;
+----+------+------+
| id | a  | b  |
+----+------+------+
| 1 |  1 |  1 |
| 2 |  2 |  2 |
| 3 |  3 |  3 |
| 4 |  4 |  4 |
| 5 |  5 |  5 |
| 6 |  6 |  6 |
| 7 |  7 |  7 |
| 8 |  8 |  8 |
| 9 |  9 |  9 |
| 10 |  10 |  10 |
+----+------+------+
10 rows in set (0.00 sec)

当我们执行下面包含group by的SQL时,查看执行计划,可以看到:

mysql> explain select id%10 as m, count(*) as c from t1 group by m limit 10;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------+
| 1 | SIMPLE   | t1  | NULL    | index | PRIMARY,a   | a  | 5    | NULL | 1000 |  100.00 | Using index; Using temporary; Using filesort |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------+
1 row in set, 1 warning (0.00 sec)

最后面有:

  • using index:覆盖索引
  • using temporary:使用了内存临时表
  • using filesort:使用了排序操作

为了更好的理解这个group by语句的执行过程,我画一个图来表示:

对照上面这个表,我们不难发现,这个group by的语句执行流程是下面这样的:

a、首先创建内存临时表,内存表里有两个字段m和c,主键是m;m是id%10,而c是统计的count(*) 个数

b、扫描表t1的索引a,依次取出叶子节点上的id值,计算id%10的结果,记为x;此时如果临时表中没有主键为x的行,就插入一个记录(x,1);如果表中有主键为x的行,就将x这一行的c值加1;

c、遍历完成后,再根据字段m做排序,得到结果集返回给客户端。(注意,这个排序的动作是group by自动添加的。)

如果我们不想让group by语句帮我们自动排序,可以添加上order by null在语句的末尾,这样就可以去掉order by之后的排序过程了。如下:

mysql> explain select id%10 as m, count(*) as c from t1 group by m order by null;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra            |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+------------------------------+
| 1 | SIMPLE   | t1  | NULL    | index | PRIMARY,a   | a  | 5    | NULL | 1000 |  100.00 | Using index; Using temporary |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+------------------------------+
1 row in set, 1 warning (0.00 sec)

可以看到,explain最后面的using filesort字样已经不见了。再来看下结果:

mysql> select id%10 as m, count(*) as c from t1 group by m;
+------+-----+
| m  | c  |
+------+-----+
|  0 | 100 |
|  1 | 100 |
|  2 | 100 |
|  3 | 100 |
|  4 | 100 |
|  5 | 100 |
|  6 | 100 |
|  7 | 100 |
|  8 | 100 |
|  9 | 100 |
+------+-----+
10 rows in set (0.00 sec)
mysql> select id%10 as m, count(*) as c from t1 group by m order by null;
+------+-----+
| m  | c  |
+------+-----+
|  1 | 100 |
|  2 | 100 |
|  3 | 100 |
|  4 | 100 |
|  5 | 100 |
|  6 | 100 |
|  7 | 100 |
|  8 | 100 |
|  9 | 100 |
|  0 | 100 |
+------+-----+
10 rows in set (0.00 sec)

当我们不加order by null的时候,group by会自动为我们进行排序,所以m=0的记录会在第一条的位置,如果我们加上order by null,那么group by就不会自动排序,那么m=0的记录就在最后面了。

我们当前这个语句,表t1中一共有1000条记录,对10取余,只有10个结果,在内存临时表中还可以放下,内存临时表在MySQL中,通过tmp_table_size来控制。

mysql> show variables like "%tmp_table%";
+----------------+----------+
| Variable_name | Value  |
+----------------+----------+
| max_tmp_tables | 32    |
| tmp_table_size | 39845888 |
+----------------+----------+
2 rows in set, 1 warning (0.00 sec)

当我们的结果足够大,而内存临时表不足以保存的时候,MySQL就会使用磁盘临时表,整个访问的速度就变得很慢了。那么针对group by操作,我们如何优化?

01

group by优化之索引

从上面的描述中不难看出,group by进行分组的时候,创建的临时表都是带一个唯一索引的。如果数据量很大,group by的执行速度就会很慢,要想优化这种情况,还得分析为什么group by 需要临时表?

这个问题其实是因为group by的逻辑是统计不同的值出现的次数,由于每一行记录做group by之后的结果都是无序的,所以就需要一个临时表存储这些中间结果集。如果我们的所有值都是排列好的,有序的,那情况会怎样呢?

例如,我们有个表的记录id列是:

0,0,0,1,1,2,2,2,2,3,4,4,

当我们使用group by的时候,就直接从左到右,累计相同的值即可。这样就不需要临时表了。

上面的结构我们也不陌生,当我们以在某个数据列上创建索引的时候,这个列本身就是排序的,当group by是以这个列为条件的时候,那么这个过程就不需要排序,因为索引是自然排序的。为了实现这个优化,我们给表t1新增一个列z,如下:

mysql> alter table t1 add column z int generated always as(id % 10), add index(z);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> select z as m, count(*) as c from t1 group by z;
+------+-----+
| m  | c  |
+------+-----+
|  0 | 100 |
|  1 | 100 |
|  2 | 100 |
|  3 | 100 |
|  4 | 100 |
|  5 | 100 |
|  6 | 100 |
|  7 | 100 |
|  8 | 100 |
|  9 | 100 |
+------+-----+
10 rows in set (0.00 sec)

mysql> explain select z as m, count(*) as c from t1 group by z;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra    |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE   | t1  | NULL    | index | z       | z  | 5    | NULL | 1000 |  100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

我们新增字段z,z的值是id%10之后的值,并且创建索引,再通过group by对这个z列进行分组,可以看到,结果中已经没有临时表了。

所以,使用索引可以帮助我们去掉group by依赖的临时表

02

group by优化---直接排序

如果我们已经知道表的数据量特别大,内存临时表肯定不足以容纳排序的时候,其实我们可以通过告知group by进行磁盘排序,而直接跳过内存临时表的排序过程。

其实在MySQL中是有这样的方法的:在group by语句中加入SQL_BIG_RESULT这个提示(hint),就可以告诉优化器:这个语句涉及的数据量很大,请直接用磁盘临时表。当我们使用这个语句的时候,MySQL将自动利用数组的方法来组织磁盘临时表中的字段,而不是我们所周知的B+树。关于这个知识点,这里给出官方文档的介绍:

SQL_BIG_RESULT or SQL_SMALL_RESULT can be used with GROUP BY or DISTINCT to tell the optimizer that the result set has many rows or is small, respectively. For SQL_BIG_RESULT, MySQL directly uses disk-based temporary tables if they are created, and prefers sorting to using a temporary table with a key on the GROUP BY elements. For SQL_SMALL_RESULT, MySQL uses in-memory temporary tables to store the resulting table instead of using sorting. This should not normally be needed.

整个group by的处理过程将会变成:

a、初始化sort_buffer,确定放入一个整型字段,记为m;

b、扫描表t1的索引a,依次取出里面的id值, 将 id%100的值存入sort_buffer中;

c、扫描完成后,对sort_buffer的字段m做排序(如果sort_buffer内存不够用,就会利用磁盘临时文件辅助排序);

d、排序完成后,就得到了一个有序数组。类似0,0,0,1,1,2,2,3,3,3,4,4,4,4这样

e、根据有序数组,得到数组里面的不同值,以及每个值的出现次数。

昨天的文章中我们分析了union 语句会使用临时表,今天的内容我们分析了group by语句使用临时表的情况,那么MySQL究竟什么时候会使用临时表呢?

MySQL什么时候会使用内部临时表?

1、如果语句执行过程可以一边读数据,一边直接得到结果,是不需要额外内存的,否则就需要额外的内存,来保存中间结果;

2、如果执行逻辑需要用到二维表特性,就会优先考虑使用临时表。比如union需要用到唯一索引约束, group by还需要用到另外一个字段来存累积计数。

以上就是MySQL group by语句如何优化的详细内容,更多关于MySQL group by优化的资料请关注我们其它相关文章!

(0)

相关推荐

  • mysql case when group by 实例详解

    mysql 中类似php switch case 的语句. select xx字段, case 字段 when 条件1 then 值1 when 条件2 then 值2 else 其他值 END 别名 from 表名; 下面是一个分组求和示例: select sum(redpackmoney) as stota,ischeck from t_redpack group by isCheck 使用case when : select sum(redpackmoney) as stota, (CAS

  • MySQL优化GROUP BY方案

    执行GROUP BY子句的最一般的方法:先扫描整个表,然后创建一个新的临时表,表中每个组的所有行应为连续的,最后使用该临时表来找到组并应用聚集函数(如果有聚集函数).在某些情况中,MySQL通过访问索引就可以得到结果,而不用创建临时表.此类查询的 EXPLAIN 输出显示 Extra列的值为 Using index for group-by. 一. 松散索引扫描 1.满足条件 查询针对一个表.  GROUP BY 使用索引的最左前缀.  只可以使用MIN()和MAX()聚集函数,并且它们均指向相

  • mysql group by 对多个字段进行分组操作

    在平时的开发任务中我们经常会用到MYSQL的GROUP BY分组, 用来获取数据表中以分组字段为依据的统计数据. 比如有一个学生选课表,表结构如下: Table: Subject_Selection Subject Semester Attendee --------------------------------- ITB001 1 John ITB001 1 Bob ITB001 1 Mickey ITB001 2 Jenny ITB001 2 James MKB114 1 John MKB

  • MySQL优化GROUP BY(松散索引扫描与紧凑索引扫描)

    满足GROUP BY子句的最一般的方法是扫描整个表并创建一个新的临时表,表中每个组的所有行应为连续的,然后使用该临时表来找到组并应用累积函数(如果有).在某些情况中,MySQL能够做得更好,即通过索引访问而不用创建临时表.        为GROUP BY使用索引的最重要的前提条件是所有GROUP BY列引用同一索引的属性,并且索引按顺序保存其关键字.是否用索引访问来代替临时表的使用还取决于在查询中使用了哪部分索引.为该部分指定的条件,以及选择的累积函数.        由于GROUP BY 实

  • MySQL去重该使用distinct还是group by?

    前言 关于group by 与distinct 性能对比:网上结论如下,不走索引少量数据distinct性能更好,大数据量group by 性能好,走索引group by性能好.走索引时分组种类少distinct快.关于网上的结论做一次验证. 准备阶段屏蔽查询缓存 查看MySQL中是否设置了查询缓存.为了不影响测试结果,需要关闭查询缓存. show variables like '%query_cache%'; 查看是否开启查询缓存决定于query_cache_type和query_cache_

  • MySQL中使用group by 是总是出现1055的错误(推荐)

    因为在MySQL中使用group by 是总是出现1055的错误,这就导致了必须去查看是什么原因了,查询了相关的资料,现在将笔记记录下来,以便后面可以参考使用: sql_mode:简而言之就是:它定义了你MySQL应该支持的sql语法,对数据的校验等等 select @@sql_mode:使用该命令我们可以查看我们当前数据库的sql_mode mysql> select @@sql_mode; +--------------------------------------------------

  • Mysql5.7及以上版本 ONLY_FULL_GROUP_BY报错的解决方法

    近期在开发过程中,因为项目开发环境连接的mysql数据库是阿里云的数据库,而阿里云的数据库版本是5.6的.而测试环境的mysql是自己安装的5.7.因此在开发过程中有小伙伴不注意写了有关group by的sql语句.在开发环境中运行是正常的,而到了测试环境中就发现了异常. 原因分析:MySQL5.7版本默认设置了 mysql sql_mode = only_full_group_by 属性,导致报错. 其中ONLY_FULL_GROUP_BY就是造成这个错误的罪魁祸首了,对于group by聚合

  • 基于mysql实现group by取各分组最新一条数据

    前言: group by函数后取到的是分组中的第一条数据,但是我们有时候需要取出各分组的最新一条,该怎么实现呢? 本文提供两种实现方式. 一.准备数据 http://note.youdao.com/noteshare?id=dba748092a619be0a8f160ccf6e25a5f&sub=FD4C1C7823CA440DB360FEA3B4A905CD 二.三种实现方式 1)先order by之后再分组: SELECT * FROM (SELECT * from tb_dept ORDE

  • Mysql升级到5.7后遇到的group by查询问题解决

    发现问题 最近在将mysql升级到mysql 5.7后,进行一些group by 查询时,比如下面的 SELECT *, count(id) as count FROM `news` GROUP BY `group_id` ORDER BY `inputtime` DESC LIMIT 20 就会报如下错误: SELECT list is not in GROUP BY clause and contains nonaggregated column 'news.id' which is not

  • MySQL group by语句如何优化

    在MySQL中,新建立一张表,该表有三个字段,分别是id,a,b,插入1000条每个字段都相等的记录,如下: mysql> show create table t1\G *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int(11) NOT NULL, `a` int(11) DEFAULT NULL, `b` int(1

  • MySQL中join语句怎么优化

    目录 Simple Nested-Loop Join Block Nested-Loop Join Index Nested-Loop Join 如何选择驱动表? Simple Nested-Loop Join 我们来看一下当进行 join 操作时,mysql是如何工作的.常见的 join 方式有哪些? 如图,当我们进行连接操作时,左边的表是驱动表,右边的表是被驱动表 Simple Nested-Loop Join 这种连接操作是从驱动表中取出一条记录然后逐条匹配被驱动表的记录,如果条件匹配则将

  • MySQL Group by的优化详解

    一个标准的 Group by 语句包含排序.分组.聚合函数,比如 select a,count(*) from t group by a ;  这个语句默认使用 a 进行排序.如果 a 列没有索引,那么就会创建临时表来统计 a和 count(*),然后再通过 sort_buffer 按 a 进行排序. 标准的执行流程 结构: create table t1(id int primary key, a int, b int, index(a)); delimiter ;; create proce

  • MySQL中Distinct和Group By语句的基本使用教程

    MySQL Distinct 去掉查询结果重复记录 DISTINCT 使用 DISTINCT 关键字可以去掉查询中某个字段的重复记录. 语法: SELECT DISTINCT(column) FROM tb_name 例子: 假定 user 表有如下记录: uid username 1 小李 2 小张 3 小李 4 小王 5 小李 6 小张 SQL 语句: SELECT DISTINCT(username) FROM user 返回查询结果如下: username 小李 小张 小王 提示 使用

  • MySQL对limit查询语句的优化方法

    当我们的网站达到一定的规模时,网站的各种优化是必须要进行的.而网站的优化中,针对数据库各种优化是最重点的了.下面作者将要和大家分享一下MySQL数据库中的查询语句有关limit语句的优化. 大家都知道一般limit是用在分页的程序的分页上的,当你的应用数据量够小的时候,也许你感觉不到limit语句的任何问题,但当查询数据量达到一定程度的时候,limit的性能就会急剧下降.这个是通过大量实例得出来的结论. 下面通过具体的案例来说明,这里是对同一张表在不同的地方取10条数据: (1)offset比较

  • MySQL中insert语句的使用与优化教程

    MySQL 表中使用 INSERT INTO SQL语句来插入数据. 你可以通过 mysql> 命令提示窗口中向数据表中插入数据,或者通过PHP脚本来插入数据. 语法 以下为向MySQL数据表插入数据通用的 INSERT INTO SQL语法: INSERT INTO table_name ( field1, field2,...fieldN ) VALUES ( value1, value2,...valueN ); 如果数据是字符型,必须使用单引号或者双引号,如:"value"

  • MySQL数据库分组查询group by语句详解

    一:分组函数的语句顺序 1 SELECT ... 2 FROM ... 3 WHERE ... 4 GROUP BY ... 5 HAVING ... 6 ORDER BY ... 二:WHERE和HAVING筛选条件的区别 数据源 位置 关键字 WHERE 原始表 ORDER BY语句之前 WHERE HAVING 分组后的结果集 ORDER BY语句之后 HAVING 三:举例说明 #1.查询每个班学生的最大年龄 SELECT MAX(age),class FROM STU_CLASS GR

  • 关于MySQL查询语句的优化详解

    目录 MySQL 优化 子查询优化 待排序的分页查询的优化 给排序字段添加索引 给排序字段跟 select 字段添加复合索引 给排序字段加索引 + 手动回表 解决办法 排序优化 MySQL 优化 子查询优化 将子查询改变为表连接,尤其是在子查询的结果集较大的情况下: 添加复合索引,其中复合索引的包含的字段应该包括 where 字段与关联字段: 复合索引中的字段顺序要遵守最左匹配原则: MySQL 8 中自动对子查询进行优化: 现有两个表 create table Orders ( id inte

  • 对MySql经常使用语句的全面总结(必看篇)

    下面总结的知识点全是经常用的,全都是干货,好好收藏吧. /* 启动MySQL */ net start mysql /* 连接与断开服务器 */ mysql -h 地址 -P 端口 -u 用户名 -p 密码 /* 跳过权限验证登录MySQL */ mysqld --skip-grant-tables -- 修改root密码 密码加密函数password() update mysql.user set password=password('root'); SHOW PROCESSLIST -- 显

  • Mysql慢查询优化方法及优化原则

    1.日期大小的比较,传到xml中的日期格式要符合'yyyy-MM-dd',这样才能走索引,如:'yyyy'改为'yyyy-MM-dd','yyyy-MM'改为'yyyy-MM-dd'[这样MYSQL会转换为日期类型] 2.条件语句中无论是等于.还是大于小于,WHERE左侧的条件查询字段不要使用函数或表达式或数学运算 3.WHERE条件语句尝试着调整字段的顺序提升查询速度,如把索引字段放在最前面.把查询命中率高的字段置前等 4.保证优化SQL前后其查询结果是一致的 5.在查询的时候通过将EXPLA

随机推荐