君哥聊技术

面试官:MySQL 使用 group by 语句时发现执行很慢,可能是什么原因?

大家好,我是君哥。

使用 MySQL 时,group by 是我们经常会用到的分组语句,可以帮我们做各种聚合统计工作。但有时候会发现 group by 语句执行很慢,可能是什么原因呢?今天来介绍一下。

1.简介

下面我们创建一张员工表:

CREATETABLE`db_staff` (
`staff_id` i  nt(8) NOTNULL AUTO_INCREMENT COMMENT'员工编号',
`id_no`varchar(20) DEFAULTNULLCOMMENT'员工姓名',
`name`varchar(20) DEFAULTNULLCOMMENT'员工姓名',
`email`varchar(200) DEFAULTNULLCOMMENT'邮件地址',
`age`tinyint(3) DEFAULTNULLCOMMENT'年龄',
`sex`tinyint(1) DEFAULT'0'COMMENT'性别,0:男 1:女',
`address`varchar(300) DEFAULTNULLCOMMENT'家庭住址',
`create_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'创建时间',
`update_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'更新时间',
  PRIMARY KEY (`staff_id`),
KEY`union_idno_name_email` (`id_no`,`name`,`email`)
) ENGINE=InnoDB AUTO_INCREMENT=1001DEFAULTCHARSET=utf8

然后往表中插入 20 条数据:

Image

我们用一个简单的分组函数,对年龄(age)进行分组

SELECT age,COUNT(age) ca FROM db_staff GROUPBY age;

查询结果如下图:Image

可以看到,使用 group by 语句非常方便地对各年龄的员工数量做了统计。

2.查询分析

那使用 group by 语句时为什么会执行慢呢? 我们看一下这条 sql 的执行计划:

Image

从执行计划可以看到,这条 sql 没有走索引,并且使用到了临时表(Using temporary)和排序(Using filesort)。

注意:filesort 很容易被理解成文件排序,其实不然,MySQL 所有不走索引的排序统称为 filesort,即使数据完全在内存中排序,执行计划 Extra 也会显示 Using filesort。

下面看一下这个语句的执行流程: 

1. 创建一个内存临时表,表里有两个字段 age 和 ca,其中 age 字段是主键。

2. 扫描要查询的表中记录,取出 age 字段;

3. 如果临时表中有这条 age(比如 ag=20) 的记录,则 ca 值加 1,否则插入一条新的记录,比如(age=30,ca=1);

4. 根据 age 做排序,将结果返回。

3.如何优化

3.1 是否需要排序

在 MySQL 8.0 以前,GROUP BY 默认是会对分组字段做排序的,即使 sql 中没写 ORDER BY,也会排序。

而且,内存中排序要用到sort_buffer,如果 sort_buffer 内存不够,就需要依靠磁盘临时表辅助排序,非常影响性能。

如果想要 sql 语句不排序,可以在 sql 尾部加 order by null,修改后的 sql 如下:

SELECT age,COUNT(age) ca FROM db_staff GROUPBY age ORDERBYNULL;

修改后再看执行计划,Extra 字段中没有了 Using filesort。

3.2 走索引

对分组字段加索引是最好的优化方法。我们对 age 字段加一个索引:

ALTERTABLE db_staff ADDKEY age_idx(age);

修改后我们再看一下执行计划

Image给 age 字段加上索引后,就不走临时表和 filesort了。

3.3 磁盘临时表

前面提到过,如果 sort_buffer 内存不够,就需要依靠磁盘临时表辅助排序。为了避免使用磁盘临时表,可以考虑减小结果集,或者临时增加 sort_buffer 大小。

对于内存临时表也一样,如果内存不够,就需要依靠磁盘临时表,可以通过修改tmp_table_size 参数来避免使用磁盘临时表。

3.4 应用层分组

对于非常复杂的聚合,可以考虑在应用层通过代码分批处理,利用多线程并发处理能力提高效率。这样可以减小数据库压力。

3.5 物理视图

对于超大表,可以考虑增加物理视图来代替 sql 分组,或者使用大数据工具。这样可以同时减少数据库和应用服务的压力,但也带来了额外维护物理视图的工作量,结果集时效性低也不高。

4.总结

group by 语句是非常好用的分组聚合函数,但如果使用不上覆盖索引,效率可能会非常低,尤其是表中数据量比较大的情况下。可以参考本文的方法进行优化。

往期精彩:
又老性能又差,为什么好多公司依然选择 RabbitMQ?

45 个知识点,带你入门消息队列!

引入了 Disruptor 后,系统性能大幅提升!

阿里二面:聊聊 MySQL 主从同步方案的优缺点

面试官:MySQL双主架构有什么优缺点?

面试官:使用 MySQL 时你遇到过哪些索引失效的场景

面试官:MySQL表中有2千万条数据,B+树层高是多少?

面试官:MySQL JOIN 表太多,你有哪些优化思路?

感谢阅读,如果对你有帮助,请点赞和在看。欢迎加我微信:zhujinjun86。

号内回复 seata,下载《阿里分布式中间件Seata从入门到精通》

号内回复 beijing,下载我总结的北京上百家知名科技公司

号内回复 aqs,下载《40张图精通Java AQS》