聊聊MySQL的group by分组 #W02

https://blog.csdn.net/u012671917/article/details/79837009


最近接触了报表业务,里面有各种分组、求和操作。整理大脑中对分组的知识碎片,并查阅相关资料,在这里对group 做一个知识的梳理。

什么是分组?

假设有一张表articles, 有 id(主键)、author_id(作者)、type_id(类型)、created_at等字段。

[sql]  view plain  copy
  1. select author_id, count(id) from articles group by author_id;  

上面就是一个简单的分组操作,它会输出所有的author_id(不会重复)以及每个author_id拥有的文章数量。类似下面:

author_id  count

13455        10

13456        5

可以看出分组是一个聚合操作,按照group by 后面的字段进行聚合,然后可以对聚合后的集合进行分别操作,类似上面的求和。 执行的内容大概是: 先按照author_id 来进行聚合,表中有多少个唯一的author_id就分为多少个组, 不同的列如果author_id相同就归为同一组,不然则为另一组。然后可以对这些组执行一些操作:

[sql]  view plain  copy
  1. select author_id, max(created_at) from articles group by author_id;  

求出每个组(每个author)中最大的的创建时间。这样可以查询每个author最近一次发表文章是什么时候。

除了聚合之外,执行分组操作的字段在输出时值是唯一的,不会重复。所以分组大概可以达到三个结果:

  1. 按照某种方式聚合,并可以对聚合后的集合进行部分操作,如计算集合元素总数,计算最大值等
  2. 会对执行分组操作的字段进行唯一值提取,有去重的效果。类似distinct
  3. 分组的结果会按照被分组的字段进行排序(输出时可以另外指定排序方式)

MySQL如何执行分组?

 主要有这几种方式:1. 临时表   2.索引

先来看看临时表方式,在上面的例子中,MySQL会创建一个临时的中间表,然后遍历全表进行分组操作,每一个分组都会在临时表中先保存一条对应的记录,在处理剩下的记录时,会把对应的行信息更新到临时表相应的记录中,如count计数,这可能会产生大量的磁盘IO。 分组完成后默认会将临时表的数据按照分组字段进行filesort,保证按照分组字段有序输出。

很显然,这种操作,在数据量较大的情况下,性能是非常低下的,除了全表扫描,还有临时表的大量磁盘IO,以及filesort。

当然,使用索引是更好的方案。

索引与分组

在很多情况下,如果分组字段上有索引,是可以避免创建临时表的。现在假设在author_id上创建了索引,分组的情况会有以下改变:

  • 从全表扫描变为顺序扫描索引。
  • 不用创建临时表,不用filesort

如果,获取的所有数据都可以从覆盖索引中获取的话,性能会有极大的提升,否则,会通过索引回表查询相应的数据,这样也会造成大量的随机IO,和临时表相比值少了临时表数据更新和filesort的消耗,性能还是不够乐观。大多数情况下,分组操作中同时有索引和临时表参与。

在有良好的索引的情况下,不会创建临时表,也较好的性能体验,这里面也主要分为两种: 

  1. 松散索引扫描
  2. 紧凑索引扫描

这里只讨论一下这两者的区别:

根据官方文档,松散索引定义:

 This property enables use of lookup groups in an index without having to consider all keys in the index that satisfy all WHERE conditions. This access method considers only a fraction of the keys in an index, so it is called a loose index scan.

紧凑索引扫描:

A tight index scan may be either a full index scan or a range index scan, depending on the query conditions.

主要的差别在于,前者只扫描了部分少量的索引就完成了分组操作,后者则进行了全索引扫描或范围扫描。或者说,前者中分组操作和范围预测是同时进行的,后者是先进行范围扫描后,再进行分组。这个究竟是个什么意思,在这里我们稍微讨论一下。

紧凑扫描的例子:(有索引 idx(c1,c2,c3),下同)

[sql]  view plain  copy
  1. select c1,c2,c3 from t1 where c1 = ‘a’ group by c2,c3  

这里需要先对组合索引进行范围扫描,找到所有的以'a'开头的集合,也就是会扫描以'a'开头的所有索引中的每一个值,利用索引的有序性进行有序扫描,从而实现分组的操作。

松散索引扫描的例子:

[sql]  view plain  copy
  1. select c1,c2  from t1 group by c1,c2;  

这里面,索引扫描的个数和分组的数量一样,也就是说,索引扫描次数 =  c1,c2组合唯一的数量, 当有多个c1,c2时,比如有这样的组合:  1,2,3   1,2,4  1,2,7  (值的顺序和索引顺序一致,c1,c2,c3) ,有多个c1,c2 的重复,松散模式只读取第一个,不会管后面的重复值,直接跳到去读取下一组不同的c1,c2的值。 也就是说整个过程只扫描了少量的索引,没有进行全索引或范围扫描。

当然,可以看出,松散模式性能更好。 为了更加深入理解,我们假设一种情况,在松散模式下,c1和c2的组合值是唯一的,也就是没有像上面那样重复,那么,以我的理解,它整个过程也相当于扫描了整个索引(不管c3列)。当然如果是这样,也就不用分组了。

distinct与分组

MySQL中常用的distinct也能实现部分group by 操作,这两者之间其实是非常类似的。distinct的实现原理与分组几乎一致,也可以使用松散/紧凑索引进行扫描,在不能只依靠索引的时候,也会像分组那样建立临时表。

差别在于,在用临时表的时候,distinct不用排序,也就是不会用filesort。


猜你喜欢

转载自blog.csdn.net/lppl010_/article/details/80296384
今日推荐