社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

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

脚本之家 • 2 周前 • 36 次点击  
脚本之家 设为“星标
第一时间收到文章更新
图片
来源 | 君哥聊技术(ID:gh_1f109b82d301)

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

1.简介

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

CREATE TABLE`db_staff` (
`staff_id` i  nt(8NOTNULL AUTO_INCREMENT COMMENT'员工编号',
`id_no`varchar(20DEFAULTNULLCOMMENT'员工姓名',
`name`varchar(20DEFAULTNULLCOMMENT'员工姓名',
`email`varchar(200DEFAULTNULLCOMMENT'邮件地址',
`age`tinyint(3DEFAULTNULLCOMMENT'年龄',
`sex`tinyint(1DEFAULT'0'COMMENT'性别,0:男 1:女',
`address`varchar(300DEFAULTNULLCOMMENT'家庭住址',
`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 条数据:

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

SELECT age,COUNT(age) ca FROM db_staff GROUP BY age;

查询结果如下图:

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

2.查询分析

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

从执行计划可以看到,这条 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 GROUP BY age ORDER BY NULL;

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

3.2 走索引

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

ALTER TABLE db_staff ADD KEY age_idx(age);

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

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

3.3 磁盘临时表

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

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

3.4 应用层分组

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

3.5 物理视图

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

4.总结

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

图片
  推荐阅读:
  1. 面试官:使用 MySQL 时你遇到过哪些索引失效的场景?
  2. MySQL datetime 类型精度设置踩坑
  3. 阿里一面:MySQL中,一个表最多支持多少个索引?64?16个?一个联合索引最多只能多少列?
  4. MySQL 复制延迟?请按以下步骤排查……

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

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/183468
 
36 次点击