您当前的位置: 首页 >  sql

wu@55555

暂无认证

  • 2浏览

    0关注

    201博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【mysql进阶】查询优化原理与方案(六)

wu@55555 发布时间:2022-06-12 12:55:23 ,浏览量:2

0. 引言

mysql中查询操作是重中之重,大部分优化的核心都为了提高查询效率。我们之前讲解了针对索引的优化,间接也说明了一些查询操作的优化,今天我们就专门针对查询进行优化方案讲解

1. 影响查询的原因

首先我们要知道查询慢的原因是什么,才能知道怎么去调优。影响查询的原因主要有以下几点:

  • 网络:调用mysql进行查询,首当其冲的就是网络连接,网络不好,直接影响了查询快慢。同时数据量大网络传输的成本也高,同样效率也低。
  • CPU:CPU占用过高时,mysql的执行效率就会降低,自然而然影响查询效率
  • 磁盘IO:mysql本身基于磁盘进行存储,如果磁盘的读写性能下降,那么访问数据的速度自然就下降了。同时如果读取的数据页碎片化太严重也会使得读取性能下降
  • 上下文切换:CPU给每个任务一定的服务时间,当时间片转换的时候会把当前任务的状态保存下来,同时加载下一个任务,这个过程叫做上下文切换,不同任务间的切换也是会消耗时间的
  • 系统调用时间
  • 生成统计信息
  • 锁等待时间

当然上述列举的这些原因并不是全部,但大部分的性能低下的查询都可以通过减少数据量的方式来进行优化

2. 查询优化 2.1 避免不必要的数据查询
  • 1、是否查询了不需要的数据 根据业务要求,返回业务需要的数据即可,不要去查询多余的列

  • 2、多表关联时,是否返回了全部列 这一点很多同学比较容易忽略,觉得我在最外层已经限制了返回的列,但是在关联查询的子查询语句中并没有限制,可能只需要其中某几列,但是却在子查询中查询了所有列

  • 3、避免使用select * 不要用select *,哪怕你真的要查询全部数据,也显示的将所有列名书写出来,这样做的好处是当后续有新增字段时,也能知道哪些进行了查询。而不会将新增的不需要的字段也一起查询出来。 随着持久层框架的引入,很多同学喜欢利用持久层框架如mybatis-plus提供的更加简单的CRUD接口方法来实现查询,但是却常常忘记声明要查询的列.select(column...),也与直接使用select * 也没什么两样

  • 4、重复查询相同的数据 查询频次较高的数据考虑使用缓存,不要觉得mysql是万能的。

2.2 使用limit来限制查询

我们常用limit来实现分页,可能好多同学不知道我们还可以用limit来优化查询。

我们用实际的案例来体现它的作用:

新增用户功能中,每次保存要求校验该用户账户名是否存在,如果已经存在不允许新增

这种类似的需求很多,想想你自己会怎么实现?

很多同学的选择是我直接根据账号名查询不就好了吗

select * from user where name = 'xxx'

可能看了咱们上节内容的同学会马上反应过来:要避免使用select *,于是乎:

select name from user where name='xxx'

但这样就完善了吗?看看返回的数据,name?这是我们想要的吗?我们只是要判断下这个’xxx’的name是否存在,而不是要把它查询出来,所以实际上我们是要看name='xxx’的数据的数量是否大于0,于是我们可以用count(*)来作为查询结果,比起查询具体的值来,更加轻便

select count(*) from user where name='xxx'

到这里就是我们优化的极限了吗?并不是!想想业务,实际上是只要一个布尔值,也就是说只要判断结果是否>0,只要找到一条满足的结果,我就不用再往后查了。有的同学可能会说,已经做了唯一性校验了,肯定只有一条满足的结果呀。但mysql可不会这么想,它接受到的指令是什么:name='xxx’的数据有多少行

而我们不需要全部结果,只需要判断是否存在即可,于是我们可以利用了limit语句,使得在查询出第一条后就不用继续后续的查询了

select count(*) from user where name='xxx' limit 1

这种方式适用于: 1、我们明确知道我们要查询的数据的目标数量时 2、关联查询的子查询中,通过limit来限定子查询的返回数据数量,当然前提是满足业务需求

另外还需要注意的是,有的同学在写sql时会注意使用limit来优化,但使用mybatis-plus等持久层框架时又忘记了,这也需要留心!

limit的start值太大时,还是会进行全表查询 我们在上一章也说明了索引失效的几种情况,当查询的数据量多大时,索引就会失效。同理当limit中的start过大时,就会导致mysql需要扫描大量的数据才能到达start位置,反而回退到了全表扫描。所以尽量避免深度分页,但如果无法避免深度分页时,我们可以通过其他的手段来优化,这一点我们将在后续讲解。

2.3 使用索引覆盖

上一章我们介绍了索引覆盖的概念,对于查询频次较高的列,我们可以建立索引,这样就可以利用索引覆盖机制,提高查询效率。

比如用户表中,要经常获取用户列表数据,就需要查询id和name。则我们就可以在name上建立索引,通过select id,name from user来进行查询,如此可以从name索引树中获取到数据,查询效率大大提高。

2.4 查询大量数据,但返回少量数据优化方式

针对于需要扫描大量数据,最终只返回少量数据的查询场景,比如:生成订单每月的销售数据,订单数据每天都会产生很多,但统计的结果是每月的销售额。这就是的典型的查多取少的业务场景

针对这类场景,我们的优化方法是:

  • 使用索引覆盖,把有需要的字段都添加索引
  • 改变表结构,适当的字段冗余,或者生成中间表/汇总表,利用定时任务定期计算中间表/汇总表数据
  • 重写复杂的查询语句,这个我们下面单独讨论
  • 利用搜索中间件,如ES来实现,当我们需要实时统计海量数据时,中间表/汇总表就不再能满足了,就需要引入一些中间件来实现
2.5 拆分复杂查询

针对于一些复杂查询,我们可以将其拆分为一组简单的查询,这样做的好处是:

  • 让缓存的命中率更高
  • 减少锁竞争
  • 减少冗余记录的查询

比如说如下查询

select p.id,p.name from product p left join order o on p.oid=o.id 
where o.amount > 100

可以分解为

select id from order where amount > 10;
# 这里的xxx,yyy,zzz是上述sql的查询结果
select id,name from product where id in (xxx,yyy,zzz)

不同简单查询之间结果的关联就可以放到业务层处理

但是这样的拆分,要综合考量执行效率,不要盲目进行

2.6 使用union all 替换 union

如果关联结果不要求去重,那么就用union all,因为union关键字会在查询时给关键字添加distinct,代价比union all更高

2.7 union优化

在使用union关联两张表时,如果要求筛选、排序、limit等操作,将操作放到子查询中先执行完,使得获取到更少量的结果,再来做union操作,union操作是会建立一张临时表的,如此可以减少临时表的数据,提高效率

2.8 尽可能使用关联查询代替子查询(mysql5.6以下遵循)

因为子查询会增加临时表,所以网上有很多说法是建议关联查询代替子查询,来实现子查询的优化。但这样的说法并不严谨,严格来说是mysql5.6之前可以遵循这条策略,但在mysql5.6版本后,mysql进行了优化,可以忽略该策略

2.9 深度分页优化

当limit分页的start值较大时,即深度分页时,查询会退化成全表扫描。这种情况我们可以利用一些语句的转换来实现优化

  • 数据量较大时 原写法
select * from rental limit 1000000,5;

更优的写法:使用子查询来优化

SELECT
	* 
FROM
	rental a
	JOIN ( SELECT rental_id FROM rental LIMIT 1000000, 5 ) b 
	             ON a.rental_id = b.rental_id;
总结

mysql查询的优化,取决于对于细节的不断追求,也要求大家平时开发时保持一种严谨的态度,不要觉得能够实现就行,这样的心态,永远无法将技术锤炼到更高一层,但相信看到这篇文章的你,已经有了这样的一种精益求精的态度。加油与坚持!

关注
打赏
1664985904
查看更多评论
立即登录/注册

微信扫码登录

0.0346s