如何优化慢SQL?

2023-11-16  本文已影响0人  回眸淡然笑

前言

前几天帮公司解决线上慢SQL告警问题,遇到了几个case。

下面我会结合case案例分析自己这段时间在工作上遇到的慢查询谈谈数据库如何优化慢查询。

一般我们遇到的慢sql都是索引没有正确使用导致的,所以我先介绍下索引相关知识

索引介绍

索引概念

排好序的快速查找的数据结构(我们平时说的索引,如果没有特别指明,都是指B树,其中聚集索引、次要索引、覆盖索引、复合索引、前缀索引、唯一索引默认使用的都是B+树索引,除B+树这种类型的索引外还有哈希索引等)

索引优缺点

优点:

缺点:

索引分类

索引结构

哪些情况要建索引

哪些情况不适合建索引

explain字段分析

explain是排查慢sql的一种最常用的手段

mysql> EXPLAIN SELECT 1;

[图片上传失败...(image-5d524b-1700187059535)]

id:表示select子句或者操作的顺序

select_type:主要是区分普通查询、联合查询、子查询等。

table:这一行数据是哪个表的数据

type:查询中使用了何种类型

结果值从最好到最坏:system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>all

possible_key:显示可能应用在这张表中的索引,但实际上不一定用到

key:实际上使用的索引,如果没有则为null

key_len:表示索引中使用的字节数(可能使用的,不是实际的),可通过该列查询中使用的索引的长度,在不损失精确性的情况下,长度越短越好

ref:显示索引的哪一列被用到,如果可能的话是一个常数,哪些常量被用于查找索引列上的值

rows:大致估算找出所需的记录要读取的行数

Extra:包含不适合在其他列中显示,但十分重要的的额外信息

更详细的内容,请看我之前的文章:

最完整的Explain总结,SQL优化不再困难

索引失效

order by优化

filesort有两种排序:

Case分析

案例一

in中参数太多

select * from goods_info where goods_status = ? and id in(11,22,33......)

in中id数据量比较多,导致查询的数据量比较大,这是一个比较常见的慢查询类型,并且往往在业务数据量比较少的时候这条语句不是慢查;

因为参数传进一个List集合,当参数比较多的时候,可以采用在业务层把List集合拆分为多个长度较小的集合,分多次查询,具体每一次拆长度为多少,可能需要具体根据业务及数据量进行评估

我的解决办法:业务代码增加拆分集合操作,LIMIT_SIZE设置为1000

List<List<Integer>> partitionGoodsIdList = Lists.partition(goodsIdList, LIMIT_SIZE);

当SQL的查询参数过多,我觉得可以考虑使用上述拆分的方式

案例二

返回的查询结果过多

select from goods where goods_status = ? and poi_id = ?

解决办法:将SQL修改为分页查询,并在业务代码上修改为分页查询,修改后的SQL语句如下:

select from goods where goods_status = 1 and poi_id = 11 and goods_id > 22 order by goods_id limit 2000

通过分页的方式可以降低数据量,避免慢查询,但是会从而导致一次查询请求,增加为多次查询请求,对于limit的大小需要谨慎评估

案例三

order by慢查询

SELECT * FROM order FORCE INDEX (orderid)  WHERE orderId = 11 AND status IN (0,22) ORDER BY id ASC ;

该SQL由于强制指定了使用orderId索引,但条件中并没有orderId,导致产生全表扫描(type: ALL);

如下为问题SQL的执行计划:

556730e61e3b623d64b217ecf9d1ea2b.png

直接原因是最终传给SQL查询函数的参数,orderId没有加入where子句,但forceindex一直生效

案例四

join慢查询

select * from useract join userinfo order by useracct.id desc limit 11;

对sql进行explain可以发现,因为忘写了join的on条件,这是扫全表sql,如下图:

c403c23d78f7201a3ae6bce2ad643b41.png

我们首先看type级别两个表的级别都是ALL,说明该条语句没有用到索引,做了全表扫描是最差的情况

优化:

0f6c0ed499fc53549de49459a67f5501.png

案例五

不同索引尝试

select id from goods_info where id > ? and activity_id = ? and goods_switch in(?+) limit ?
select id from goods_info where id > 123991510 and activity_id = 0 and goods_switch in (2,3) limit 1000

通过执行计划可知,该语句走的是activity_id和主键的索引,但是这种命中率比较低,大量的数据被goods_switch筛掉

解决办法:在不确定最优的索引的情况下,可以在测试环境下,分别添加不同的索引,观察执行计划及语句的执行时间。

尝试强制走主键索引,效果不佳;尝试添加activity_id_id的联合索引,效果不佳;尝试添加activity_id,goods_switch的联合索引,问题解决!

所以在不确定哪种索引是最优时,可以尝试建立不同的索引,观察语句在不同索引情况下的执行情况进行权衡。

案例六

MySQL选错索引

select * from goods_info
where goods_source = ? and goods_switch != ? and id > ? order by id limit ?

select * from goods_info  
where goods_source = 2 and goods_switch != 8 and id > 12395070 order by id limit 1000

这条语句从语句本身猜测使用的是主键索引,但是查看该语句的执行计划,发现走的索引是idx_goods_source,即走了goods_source的单列索引!

解决办法:修改SQL语句,强制走主键索引,查看执行计划,走了主键索引,查询时间大大降低。

正常情况下MySQL会选择最优的索引,但是有时候也会选错,MySQL的优化器会依据扫描行数、是否排序,索引区分度来选择最优的索引,并且扫描行数不一定完成准确,只是MySQL的一个预估值

上一篇 下一篇

猜你喜欢

热点阅读