本文已参与「新人创作礼」活动,一起开启掘金创作之路。
如何让SQL跑的更快、效率更高
系统负载高、响应慢,在DB层面,低效的SQL很有可能是罪魁祸首!
通过explain关键字诊断SQL效率
explain关键字是mysql提供用于分析sql执行的具体信息,比如 扫描类型、索引、扫描行数、排序、临时文件等等
- explain可用于SELECT, DELETE, INSERT, REPLACE以及 UPDATE语句之前;
explain输出列
| Column | JSON Name | Meaning |
|---|---|---|
| id | select_id | The SELECT identifier |
| select_type | None | The SELECT type |
| table | table_name | The table for the output row |
| partitions | partitions | The matching partitions |
| type | access_type | The join type |
| possible_keys | possible_keys | The possible indexes to choose |
| key | key | The index actually chosen |
| key_len | key_length | The length of the chosen key |
| ref | ref | The columns compared to the index |
| rows | rows | Estimate of rows to be examined |
| filtered | filtered | Percentage of rows filtered by table condition |
| Extra | None | Additional information |
其中关键的几列:
-
type: 描述表的join关系,由好到坏:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL;其中除了ALL全表扫描外,其他类型都会通过索引优化 进而减少扫描行数
-
possible_keys: 当前SQL中出现的索引, 也就是当前SQL可能会引用的索引
-
key:最终选择的索引列
-
key_len: 表示使用的索引长度,是以字节为单位
-
ref: 引用字段,如果是使用的常数等值查询,这里会显示const;如果是关联查询,这里是 驱动表的某个字段(关联查询时通常驱动表的某个字段是被驱动表中建立索引一个字段);如果条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func
-
rows: 表示的是预计扫描行数
-
filtered:表示返回结果的行数占 rows 的百分比(最大值是100);在rows一定的情况下,filtered列的值越小越好,因为对于被驱动表来说,其扫描次数由驱动表的rows x filtered决定。例如,如果rows为1000,过滤后(where子句过滤)为50.00(50%),则需要与下表连接的行数为1000 × 50% = 500 查看
-
Extra: 输出查询计划的一些额外重要信息,比如文件排序、索引等等
type字段(常见部分)
- system: 表里只有一行数据,根据条件将这行数据查询出来
- const:最多匹配表中的一行数据,通常是PRIMARY KEY or UNIQUE index查询
- eq_ref: join操作时,驱动表A与被驱动表B,A表的驱动字段f通常是B表的主键id或者B表的唯一键
- ref: 一般是通过普通索引匹配少量数据时的类型。例如,join操作时,驱动表A与被驱动表B,A表的驱动字段f通常是B表的非主键id或者B表的非唯一键,也就是只是b表的普通索引键,这一点正好和eq_ref类型互补
- ref_or_null: 与ref类型相似,不同是加了is null条件处理为空的查询
- index_merge:多索引查询,然后合并各索引搜索的结果集
- range: 通过索引查询的结果集通常是一个数据行数不小的范围
- index:这种类型的效率仅优于ALL,会先扫描索引树,如果要求返回的列可以满足要求就直接返回不用进行回表操作了,否则会将满足条件的所有数据都进行回表查询数据
- ALL: 全表扫描
Extra字段详解(常见部分)
-
Using filesort: 表示需要排序。当需要通过order by子句有序输出时,如果待排序内容超过了缓冲区大小,则会借助于文件排序的方式来完成排序操作
-
Using index: 使用覆盖索引,不需要回表。查询字段从索引树(普通索引)就可以拿到的情况就不会通过回表的方式从聚簇索引(也就是主键索引)索取数据,节约的回表操作可以减少I/O操作
-
Using index condition: 联合索引上使用索引下推尽可能多的过滤掉不满足where条件的数据,从而减少I/O操作
-
Using join buffer:join连表查询会使用到join buffer。Block Nested Loop 和 Batched Key Access算法会使用。
-
Using MRR: Multi-Range Read优化策略,通常情况下我们会通过普通索引快速定位到主键ID,然后回表从聚簇索引上获取数据;虽然在普通索引上是顺序访问,但在聚簇索引树上很有可能是通过随机访问的方式,因此,该优化策略通过将从普通索引获取的主键ID排序后,然后顺序访问的方式访问聚簇索引树
-
Using temporary:表示使用了临时表。分为内存临时表和磁盘临时表,内存临时表有一定的限制,达到限制之后需要使用磁盘临时表存储中间数据。通常可能在group by, order by 以及union操作(会去重) 可能会使用内部临时表
-
Using where:使用where条件过滤
join算法:
-
Index Nested-Loop Join(NLJ):驱动表走全表扫描,被驱动表通过索引扫描
-
Block Nested-Loop Join(BNL):被驱动字段不存在索引,因此被驱动表也只能走全表扫描,此时通过join buffer,将驱动表的数据一块块读到join buffer, 然后从被驱动表一条条读取数据做join操作(与join buffer中的数据对比),重复这两步操作,直到join操作完成;由此可见合理设置join buffer大小至关重要,join buffer越大分块放入次数越少, I/O次数也就越少。
-
Batched Key Access(BKA):是NLJ算法的优化版本。借助于MRR优化操作,将多条主键ID暂存join buffer,排序后从聚簇索引读取数据,因为磁盘顺序读取的效率也很高
参考文档 mysql5.7