为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理….

前言

在程序上线运行一段时间后,一旦数据量上去了,或多或少会感觉到系统出现延迟、卡顿等现象,出现这种问题,就需要程序员或架构师进行系统调优工作了。

其中,大量的实践经验表明,调优的手段尽管有很多,但涉及到SQL调优的内容仍然是非常重要的一环,本文将结合实例,总结一些工作中可能涉及到的SQL优化策略;

查询优化

可以说,对于大多数系统来说,读多写少一定是常态,这就表示涉及到查询的SQL是非常高频的操作;

前置准备,给一张测试表添加10万条数据

使用下面的存储过程给单表造一批数据,将表换成自己的就好了

create procedure addMyData()
 
 begin
 
  declare num int;
  set num =1;
  
  while num <= 100000 do
  
   insert into XXX_table values(
    replace(uuid(),'-',''),concat('测试',num),concat('cs',num),'123456'
   );
 
   set num =num +1;
  end while;
 
 end ;

然后调用该存储过程

call addMyData();

本篇准备了3张表,分别为学生(student)表,班级(class)表,账户(account)表,各自有50万,1万和10万条数据用于测试;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....
为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

1、分页查询优化

分页查询是开发中经常会遇到的,有一种情况是,当分页的数量非常大的时候,查询的时候往往非常耗时,比如查询student表,使用下面的sql查询,耗时达到0.2秒;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

实践经验告诉我们,越往后,分页查询效率越低,这就是分页查询的问题所在, 因为,当在进行分页查询时,如果执行 limit 400000,10 ,此时需要 MySQL 排序前4000 10 记 录,仅仅返回400000 - 4 00010 的记录,其他记录丢弃,查询排序的代价非常大

优化思路:

一般分页查询时,通过创建 覆盖索引 能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化;

1) 在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容

SELECT * FROM student t1,(SELECT id FROM student ORDER BY id LIMIT 400000,10) t2 WHERE t1.id =t2.id;

执行上面的sql,可以看到响应时间有一定的提升;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

2)对于主键自增的表,可以把Limit 查询转换成某个位置的查询

select * from student where id > 400000 limit 10;

执行上面的sql,可以看到响应时间有一定的提升;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

2、关联查询优化

在实际的业务开发过程中,关联查询可以说随处可见,关联查询的优化核心思路是,最好为关联查询的字段添加索引,这是关键,具体到不同的场景,还需要具体分析,这个跟mysql的引擎在执行优化策略的方案选择时有一定关系;

2.1 左连接或右连接

下面是一个使用left join 的查询,可以预想到这条sql查询的结果集非常大

select t.* from student t left join class cs on t.classId = cs.id;

为了检查下sql的执行效率,使用explain做一下分析,可以看到,第一张表即left join左边的表student走了全表扫描,而class表走了主键索引,尽管结果集较大,还是走了索引;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

针对这种场景的查询,思路如下:

  • 让查询的字段尽量包含在主键索引或者覆盖索引中;
  • 查询的时候尽量使用分页查询;
为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

关于左连接(右连接)的explain结果补充说明

  • 左连接左边的表一般为驱动表,右边的表为被驱动表;
  • 尽可能让数据集小的表作为驱动表,减少mysql内部循环的次数;
  • 两表关联时,explain结果展示中,第一栏一般为驱动表;
2.2 关联查询关联的字段建立索引

看下面的这条sql,其关联字段非表的主键,而是普通的字段;

explain select u.* from tenant t left join `user` u on u.account = t.tenant_name where t.removed is null and u.removed is null;
为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

通过explain分析可以发现,左边的表走了全表扫描,可以考虑给左边的表的tenant_nameuser表的“ 各自创建索引;

create index idx_name on tenant(tenant_name);

create index idx_account on `user`(account);

再次使用explain分析结果如下

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

可以看到第二行type变为ref,rows的数量优化比较明显。这是由左连接特性决定的,LEFT JOIN条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引 。

2.3 内连接关联的字段建立索引

我们知道,左连接和右连接查询的数据分别是完全包含左表数据,完全包含右表数据,而内连接(inner join 或join) 则是取交集(共有的部分),在这种情况下,驱动表的选择是由mysql优化器自动选择的;

在上面的基础上,首先移除两张表的索引

ALTER TABLE `user` DROP INDEX idx_account;
ALTER TABLE `tenant` DROP INDEX idx_name

使用explain语句进行分析

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

然后给user表的account字段添加索引,再次执行explain我们发现,user表竟然被当作是被驱动表了;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

此时,如果我们给tenant表的tenant_name加索引,并移除user表的account索引,得出的结果竟然都没有走索引,再次说明,使用内连接的情况下,查询优化器将会根据自己的判断进行选择;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

3、子查询优化

子查询在日常编写业务的SQL时也是使用非常频繁的做法,不是说子查询不能用,而是当数据量超出一定的范围之后,子查询的性能下降是很明显的,关于这一点,本人在日常工作中深有体会;

比如下面这条sql,由于student表数据量较大,执行起来耗时非常长,可以看到耗费了将近3秒;

select st.* from student st where st.classId in (
 
 select id from class where id > 100
 
);
为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

通过执行explain进行分析得知,内层查询 id > 100的子查询尽管用上了主键索引,但是由于结果集太大,带入到外层查询,即作为in的条件时,查询优化器还是走了全表扫描;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

针对上面的情况,可以考虑下面的优化方式

select st.id from student st join class cl on st.classId = cl.id where cl.id > 100;

子查询性能低效的原因

  • 子查询时,MySQL需要为内层查询语句的查询结果建立一个临时表 ,然后外层查询语句从临时表中查询记录,查询完毕后,再撤销这些临时表 。这样会消耗过多的CPU和IO资源,产生大量的慢查询;
  • 子查询结果集存储的临时表,不论是内存临时表还是磁盘临时表都不能走索引 ,所以查询性能会受到一定的影响;
  • 对于返回结果集比较大的子查询,其对查询性能的影响也就越大;

使用mysql查询时,可以使用连接(JOIN)查询来替代子查询。连接查询不需要建立临时表 ,其速度比子查询要快 ,如果查询中使用索引的话,性能就会更好,尽量不要使用NOT IN 或者 NOT EXISTS,用LEFT JOIN xxx ON xx WHERE xx IS NULL替代;

一个真实的案例

在下面的这段sql中,优化前使用的是子查询,在一次生产问题的性能分析中,发现某个tenant_id下的数据达到了35万多,这样直接导致某个列表页面的接口查询耗时达到了5秒左右;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

找到了问题的根源后,尝试使用上面的优化思路进行解决即可,优化后的sql大概如下,

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

4、排序(order by)优化

在mysql,排序主要有两种方式

  • Using filesort : 通过表索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort。buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序;
  • Using index : 通过有序的索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高;

对于以上两种排序方式,Using index的性能高,而Using filesort的性能低,我们在优化排序操作时,尽量要优化为 Using index

4.1 使用age字段进行排序

由于age字段未加索引,查询结果按照age排序的时候发现使用了filesort,排序性能较低;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

给age字段添加索引,再次使用order by时就走了索引;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....
4.2 使用多字段进行排序

通常在实际业务中,参与排序的字段往往不只一个,这时候,就可以对参与排序的多个字段创建联合索引;

如下根据stuno和age排序

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

给stuno和age添加联合索引

create index idx_stuno_age on `student`(stuno,age);

再次分析时结果如下,此时排序走了索引

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....
关于多字段排序时的注意事项

1)排序时,需要满足最左前缀法则,否则也会出现 filesort;

在上面我们创建的联合索引顺序是stuno和age,即stuno在前面,而age在后,如果查询的时候调换排序顺序会怎样呢?通过分析结果发现,走了filesort;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

2)排序时,排序的类型保持一致

在保持字段排序顺序不变时,默认情况下,如果都按照升序或者降序时,order by可以使用index,如果一个是升序,另一个是降序会如何呢?分析发现,这种情况下也会走filesort;

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

5、分组(group by)优化

group by 的优化策略和order by 的优化策略非常像,主要列举如下几个要点:

  • group by 即使没有过滤条件用到索引,也可以直接使用索引;
  • group by 先排序再分组,遵照索引建的最佳左前缀法则;
  • 当无法使用索引列时,增大 max_length_for_sort_datasort_buffer_size 参数的设置;
  • where效率高于having,能写在where限定的条件就不要写在having中了;
  • 减少使用order by,能不排序就不排序,或将排序放到程序去做。Order bygroupbydistinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的;
  • 如果sql包含了order bygroup bydistinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢;
5.1 给group by的字段添加索引

如果字段未加索引,分析结果如下,这种结果性能显然很低效

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

给stuno添加索引之后

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

给stuno和age添加联合索引

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

如果不遵循最佳左前缀,group by 性能将会比较低效

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

遵循最佳左前缀的情况如下

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

6、count 优化

count() 是一个聚合函数,对于返回的结果集,一行行判断,如果 count 函数的参数不是NULL,累计值就加 1,否则不加,最后返回累计值;

用法:count(*)count(主键)count(字段)count(数字)

如下列举了count的几种写法的详细说明

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

经验值总结

按照效率排序来看,count(字段) < count(主键 id) < count(1) ≈ count(*),所以尽量使用 count(*)

作者:逆风飞翔的小叔

来源:blog.csdn.net/congge_study/article/

details/127712927

推荐

Java面试题宝典

技术内卷群,一起来学习!!

为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理....

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。“在看”支持我们吧!

原文始发于微信公众号(Java知音):为了减少延迟和卡顿,我对 MySQL 查询做了这些优化处理….

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/132047.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!