SQL优化---慢SQL优化
于2023.3.17日重写,之前写的还是太八股文太烂了一点逻辑都没有,这次重新写了之后,感觉数据库优化还是很有必要的,之前觉得不必要是我年轻了。
一、如何定位慢SQL语句
1、通过慢查询日志查询已经执行完的SQL语句
默认慢查询日志不会开启,需要手动设置,命令为
mysql> set global slow_query_log = on;
也可以根据业务设置时间阈值以及查看慢查询日志的存储位置和文件名,自行百度
然后可以通过查询日志的方式,定位到慢SQL语句,比如使用tail命令
2、通过processlist查看正在执行的慢SQL语句
这个没什么好说的,在mysql>终端中,使用show processlist指令或者show full processlist
二、如何分析慢SQL语句
使用老朋友explain关键字explain+分析的sql语句即可,一般来说,我们需要重点关注selectType,type、rows、extra、key。
关键字 | 含义 | 常见 |
---|---|---|
selectType | 查询类型 | simple,primary,union |
type | 表连接类型 | ref,index,range,all |
rows | 预计需要扫描的行数 | 是估值 |
key | 实际使用的索引 | |
extra | 附加信息 | Using index,Using temporary,using filesort |
三、分析慢SQL语句的原因
1、大部分慢SQL语句优化都跟索引有关系,查看是不是索引设置不合理,索引失效等问题
2、一部分是sql语句本身有关系,比如深分页,in元素过多,对非索引字段进行排序等,可以考虑优化sql语句
3、小部分与sql语句无关,比如大数据表的查询确实会比较慢
四、常见的SQL优化
- 优化Order By
首先,我们先知道Order by是如何实现的,order by的功能我们都知道,就是使用By后面的一个或者多个字段进行排序,我们假设现在有这么一个需求,对周杰伦的歌曲按照播放量进行排序,查出歌手,歌曲编码,歌曲曲风,播放量等播放,很容易写出来
select singer,song_code,song_types,conuts FROM `song` where singer='周杰伦' order by counts
我们假设对singer字段加了一个索引,这样避免全表扫描,那么这条sql语句应该是这样执行的。
a. 初始化sort_buffer,放入所有select字段(可以理解是在sort_buffer这块内存中初始了select所需字段的一张表)
b. 从索引singer
找出第一条满足singer
是周杰伦的行记录的主键(不懂的同学可以看下辅助索引的概念,简单来说非主键索引的B+树叶子节点是主键索引),根据主键进行回表 取出select所需的字段放入sort_buffer中
c. 重复b操作,直到所有满足条件的记录放入sort_buffer中了,然后sort_buffer内部根据字段counts
进行快速排序,返回结果
以上这种实现方式,叫做全字段排序,观察这种实现方式,它的特点就是要把select的所有字段放入sort_buffer中,尽管只需要一个字段进行排序,它的问题也很明显,我用黄色高亮出来了,所有字段都放入sort_buffer中,如果select了好多个字段或者sort_buffer设置的太小(自行百度,没必要记住这种命令),那内存就顶不住了,得靠磁盘中的内部文件帮忙,此时外部文件使用归并排序。
解决这个问题的就是order by的第二种实现方式 rowid排序,这种实现方式也很好理解,就是sort_buffer中只放两种类型的字段,一种就是需要排序的字段,另一种就是主键(一般就是id作为主键),那么sql语句执行方式就不太一样了。
a. 初始化sort_buffer,放入需要排序的字段和id字段
b.根据辅助索引singer
找出第一条满足singer
是周杰伦的行记录的主键,根据主键进行回表,取出主键id和counts
字段放入sort_buffer中
c. 重复b操作,直到所有满足条件的记录放入sort_buffer中了,然后sort_buffer内部根据字段counts
进行快速排序
d. 根据排序后的主键id进行回表,取出select需要的字段返回
这就是rowid排序的实现方式,也很明显,这种方式需要两次回表操作,所以并不是一定会比所有字段排序方式好
那如何解决呢?答案页很简单,说到底还是我们的索引设置的不够合理。
对于这种情况优化,我们可以采用设置联合索引的方式进行优化,如果联合索引设置的好,那天然就是有序的了,就没必要进行排序了,同时也可以解决回表问题(索引覆盖),比如针对这个需求,我们可以创建这样的一个联合索引
alter table `song` add index singer_counts_song_code_song_types(singer,counts,song_code,song_types);
注意顺序,singer,counts字段一定要在前两个,如果理解不了的话,要去学习一下联合索引的结构原理。
当然这样做也有局限,比如确实不应该添加索引或者where条件不只有周杰伦也可能是周杰伦和周深的歌曲播放量排名。
- llimit深分页优化
limit深分页问题也是大数据领域常见的问题了,很多小白包括我之前并不了解limit的用法,我先简单介绍下limit的用法。 limit offset,n 先是偏移量然后才是真正读取的n条数据,其含义是扫描offset+n条数据然后抛弃前offset条数据,留下最后n条数据。
为什么说深分页不好呢,答案也很简单如果offset很大,那么很大可能会有很多无效的回表,那是因为较多场景其实是利用了辅助索引(例如上面的那个需求,主键就是无意义的id,真正有利用信息的是辅助索引),这么多无效的回表是难以接受的,常见的优化手段有:
我们可以利用子查询优化深分页,这样就避免了回表操作,例如《阿里巴巴java性能调优实战》中的例子
## 需求是返回10000条之后的20条记录
##### 未优化:
select * from `order` order by order_no limit 10000,20
##### 子查询优化:
select * from `order` where id >(select id from `order` order by order_no limit 10000,1) limit 20
id是主键,因此也没有了回表问题
或者如果我们提前知道搜索范围是10001到10020,那么我们就可以直接使用between and范围查询来避免limit深分页问题,当然前提是order by的字段是连续的,而且要知道准确需求范围(很多都是从上次查询的位子开始后n行,这就不行了)
- in内元素过多
in本身操作性能就不高,加上元素太多会造成很大的性能浪费,目前我并没有查找到常用的解决方案,我这里给出我常用的方法,如果in里面的元素还是可以枚举的,那么可以尝试union all各个条件select出的结果,如果是不能枚举的,阿里巴巴有个类Mysql的PolarDB mysql有对大量in进行优化 [阿里巴巴文章](PolarDB for MySQL优化器查询变换系列 - IN-List变换 - 陈江的文章 - 知乎 https://zhuanlan.zhihu.com/p/591601424)