Mysql如何执行GroupBy与Union

Posted 唐宋xy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql如何执行GroupBy与Union相关的知识,希望对你有一定的参考价值。

mysql在进行分组或者子查询的过程中一般会使用到内部临时表的机制来帮助完成数据的统计,而group byunion在这个过程中,也可能会使用到内部临时表来完成数据的统计

Group By的执行流程

分组字段无索引

  • 执行语句

    select id%10 as m, count(*) as c from t1 group by m;

    这个简单的分组语句是将id%10分组进 行统计,并统计每个计算之后的数据的数量,并且会按照m的结果排序后输出。

    groupBy查询

    通过explain可以看到:

    • using temporary表示在执行分组的时候使用了临时表
    • using filesort表示使用了文件排序(可能是内存排序也可能借助了磁盘文件排序,取决于sort buffer是否可以放下查询到的数据)
  • 执行流程

    • 创建内存临时表,并且表里有2个字段,一个字段m,一个字段c,分别用来统计值和对应的数量,并且主键为m
    • 扫描t1表的索引a,依次取出叶子节点上的id值(这里不借助其他索引,在主键索引上可以直接完成,所以可以使用到覆盖索引),计算id%10的结果,记为x
      • 如果临时表中已经有主键为x的数据,那么就将c+1
      • 如果临时表汇中没有主键为x的数据,那么就新增一条数据,并且c为1
    • 遍历完成之后,再根据字段m做排序,将得到的结果集返回客户端
  • 优化排序结果

    在上面的group by的执行过程中,在筛选到分组的结果集之后,默认是会对分组的字段进行排序,然后再进行返回,如果需求不需要进行排序,那么可以省略掉排序的步骤,加快sql的执行效率,防止数据量过大在排序的过程中消耗过多时间。

    • order by null

      select id%10 as m, count(*) as c from t1 group by m order by null;

      这样就跳过了最后排序的阶段,直接从临时表中取数据返回

  • 内部才能临时表与磁盘临时表

    上面的例子中,因为数据量较少,内存可以放得下,所以在使用临时表的时候,优先选择使用内存临时表。但是内存临时表的大小是有限制的,通过参数tmp_table_size控制,默认是16M。

    当分组的数据量较多,内存临时表无法放下的时候,就会将内存临时表转换为磁盘临时表,在磁盘临时表中如果还需要做排序,是非常慢的操作,因为需要操作磁盘。

分组字段为索引字段

可以看到,无论使用内存临时表还是使用磁盘临时表,group by的执行过程中都需要创建一个唯一索引的临时表来帮助完成分组的操作。如果数据量较大,执行sql就会比较慢。

因为在统计的过程中,分组的过程中不确定后续是否会出现同一数据(就是因为原数据不是有序的),所以不能直接将当前统计的数据返回客户端,需要将所有的数据扫描统计完成之后,才可以将数据返回客户端,所以需要临时表记录当前统计数据的状态。

所以,因为索引是有有序的,那么给分组的字段加索引,就可以解决分组时的问题,在不使用临时表来完成分组的统计操作。

  • 分组字段加索引

    alter table t1 add column z int generated always as(id % 100), add index(z);

    MySQL 5.7 版本支持了 generated column 机制,用来实现列数据的关联更新

    查询语句:

    select z, count(*) as c from t1 group by z;

    explain结果:

    groupBy查询结果-索引

  • 执行流程

    • 因为是按照索引进行分组,数据是有序的,所以从左向右依次进行统计,顺序累加,当遇到下一个数字的时候,就知道当前的数字已经统计完成,后面不会出现当前的数字了,可以将当前的统计结果加入到结果集中,继续统计下一个数字。

      因为是有序的,所以分组统计的时候知道当前统计的数字的开始和结束,直接将统计的结果加入到结果集中,不需要借助临时表完成数据的记录,并且也不需要额外的排序,索引本就是有序的。

可以看到,在group by的字段有索引的条件下, 分组统计既不需要临时表,也不需要额外进行排序,提高查询的效率,这也是为什么优化SQL时经常对group by的字段加索引。

大数据量分组无索引

如果可以通过加索引来完成 group by 逻辑就再好不过了。但是,如果碰上不适合创建索引的场景,这时候的 group by 要怎么优化呢

group by再执行的过程中,会先将数据放入到内存临时表,如果内存临时表不够用,会将内存临时表转换为磁盘临时表。如果已知数据量过大,那么是否可以省略内存临时表的转换过程,直接使用磁盘临时表?

Mysql提供了SQL_BIG_RESULT可以告诉优化器,当前的数据量较大,直接使用磁盘临时表。

select SQL_BIG_RESULT id%100 as m, count(*) as c from t1 group by m;

Union的执行流程

union的执行流程

  • 执行语句

    (select 1000 as f) union (select id from t1 order by id desc limit 2);

    上面的执行语句中,将两个子查询的结果进行合并,并且去重,重复的只保留一行。

    explain执行结果:

    union执行结果

    • using temporary表示在合并结果集的时候使用了临时表
  • 执行流程

    • 创建一个内存临时表,这个临时表只有一个主键字段f
    • 执行第一个子查询,将查询的结果放入到内存临时表中
    • 执行第二个子查询,依次扫描每一行数据,从主键索引上直接扫描并获取id
      • 拿到每一行的id字段,尝试插入到临时表中,如果临时表中没有这个字段就直接插入到临时表中,如果已经当前的数据了,那么就插入失败,因为主键的唯一性,那么就放弃插入当前数据,继续执行
      • 拿到下一行的id字段,继续执行上面的流程,插入成功则插入到临时表中,插入失败则继续下一行
    • 从临时表中依次拿到每一行的数据,并将数据放入到结果集中,返回客户端,并删除临时表
  • 优化

    可以看到,union的执行过程使用到了内存临时表,临时表的作用是暂存数据以及对重复的数据进行去重,保证数据的唯一性。

    如果不需要对数据进行去重,可以将union改成union all,这样就不会使用临时表。

    union all也是将两个子查询的结果集进行合并,不同的是,union all不会对结果集进行去重,有重复的数据会都返回给客户端。

    unionAll查询结果

    可以看到,只有使用覆盖索引,没有使用临时表的执行步骤。

    所以,union all再执行的过程中,不会创建临时表,而是一次执行子查询,得到的子查询的结果直接作为结果集的一部分,直接返回客户端。

小结

  • group by在使用的时候,尽量使用索引字段进行分组,查询效率很快
  • group by如果没有索引,还是要进行分组,如果没有排序的需求,可以使用order by null减少一次额外的排序
  • 如果group by的分组数据较大,可以直接使用SQL_BIG_RESULT告诉优化器使用磁盘临时表,防止内存临时表再次转换为磁盘临时表的额外操作。
  • 如果对union的结果集没有去重的要求,可以使用union all

公众号
微信公众号「指尖上的代码」,欢迎关注~

原创不易, 点个赞再走呗~ 欢迎关注,给你带来更精彩的文章!

你的点赞关注是写文章最大的动力~

以上是关于Mysql如何执行GroupBy与Union的主要内容,如果未能解决你的问题,请参考以下文章

mysql union all 的 bug

MySQL:Group By的具体介绍与内部执行原理

Mysql UNION 和 GROUP BY

MySQL中wheregroup byhavingorder by后面能使用字段别名吗?

20180514早课记录09-MySQL&Hadoop

SQL union all 与 group by