vlambda博客
学习文章列表

MySql慢查优化之路|01-执行计划

引言
平时我们的系统是通过数据库来持久化业务数据的,对于一名开发者而言,掌握数据库的正确使用和编程一样的重要。尤其对于互联网企业,针对多用户多并发的toC系统,一般使用的分布式微服务架构。服务与服务之间错杂的调用链路,尤其作为最基础的服务提供方,如果在查询数据库时发生慢查询,可能会导致一系列的业务调用方超时,就像“多米诺效应”,造成不可限量的损失。所以,掌握一些数据库优化的知识很重要。

MySql慢查优化之路|01-执行计划

我们在选择数据库时,优先会想到关系型数据库,因为它具备以下优点
  • 保持数据的一致性,可使用事务处理;

  • 字段固定,数据更新的成本低;

  • 可以进行一些复杂查询。

当然,如果数据量过大(相关评估也没有特定的标准,一般MySql对于存储本身没有做什么限制,业内(参见阿里巴巴的《Java开发手册》)一般按照一张表的数据达到500W行或2G存储容量作为参考,当然这也取决于你的表设计和服务器性能),可以采用分库分表或者采用nosql数据库等方式(后期可以讲讲)。本系列我主要通过当前最常用之一的关系型数据库MySql谈谈个人的一些慢查询优化技巧。

平时我们在接到一个需求/项目,在完成需求分析后进入方案设计阶段时,就会考虑到表结构的设计及一些主功能的sql语句编写。 这时我们就需要考虑后期大数据业务量的情况,而预见并设计的辅助工具就是使用执行计划,这也是我们需要掌握的基本技巧。 网上关于执行计划的文章很多,但感觉要么篇幅不全,要么写得不够详细,下面是我个人整理相对全面的执行计划知识点。
1.使用方法
这里我用的QA环境的帖子表进行演示(预估数据量5W左右)。
其实很简单,一般直接在我们的SQL语句前加 explain 关键字执行即可:

2.字段分析

上面展示的字段具体是什么含义,有什么意义,哪些可以作为我们设计优化参考点,我们细细来看。

2.1 id 列

用于记录唯一标识的同时,可以表明执行顺序,规则如下:

  • 数字越大越先执行;

  • 相等时,按照由上至下的顺序执行;

  • 如果为null,代表一个结果集,无需使用它来查询。

2.2 select_type 列

表示查询类型,有如下:

条件 说明
simple

没有union和子查询的简单查询

有连接查询时,外查查询为simple(仅有一个)
primary 有union或子查询 位于最外层查询即为primary(仅有一个)
union uion连接查询 第一个查询是dervied派生表,其他都属于union
union result 包含union的结果集 在union和union all语句中,因为它不需要参与查询,所以id字段为null
dependent union union,出现在union 或union all语句中

受到外部查询的影响(mysql优化器会将in优化成exists)

例:select * from bbs_topic where id in(select id from bbs_topic union select id from bbs_topic);    --会转化成 --
select * from bbs_topic a where EXISTS (select 1 from bbs_topic where id = a.id union select id from bbs_topic where id = a.id);

subquery 除了from包含的子查询 除from子句中包含子查询外,其他地方出现的子查询都可能是
dependent subquery dependent union 会受到外部表查询的影响
derived from子句中出现的子查询(称“派生表”) 其他数据库中可能叫做内联视图或嵌套select
materialization  通过将子查询结果作为一个临时表来加快查询执行速度(称“物化”) 正常来说是常驻内存,下次查询会再次引用临时表

2.3 table 列

  1. 一般显示的查询表名,有别名会显示别名;

  2. 如果不涉及表数据操作,显示为null;

  3. 如果显示数据外层带尖括号,代表查询的是临时表(后面带N,代表相关id为N的查询产生的)。

2.4 type 列

访问类型,表示找到所查询数据的方法,有如下:

条件 说明
system 表的存储引擎是myisam或memory时,当表是空表或只存在一条数据 无需磁盘IO,速度非常快
const

当查询最多匹配一行时

常出现于where条件是=的情况,只有=的右边是常量的时候才会走const

eq_ref 多表关联查询时,(第二张表)根据唯一非空索引进行查询的情况(返回必须一条)
ref 多表关联查询时,根据唯一非空索引进行查询的情况
fulltext

全文索引检索

优先级很高,与普通索引同时存在时会优先选择全文索引(不惜代价)

ref_or_null 同ref,多了null值比较
unique_subquery

用于where中带in的子查询

子查询返回不重复且唯一
index_subquery

in形式子查询中用到辅助索引或常数列表

子查询可返回重复值,可用索引去重
range

索引范围扫描


常见于使用>,<,is null,between ,in ,like等运算符的查询中
index_merge

表示查询使用了两个以上的索引,最后取交集或者并集


常见and ,or的条件使用了不同的索引,官方排序这个在ref_or_null之后,但是实际上由于要读取多个索引,性能可能大部分时间都不如range
index 索引全表扫描,把索引从头到尾扫一遍 常见于使用索引列就可以处理不需要读取数据文件的查询、可以使用索引排序或者分组的查询
all 全表扫描数据文件,再在server层进行过滤返回符合要求的记录

总结说明:

  • 性能从好到差依次:system,const,eq_ref,ref,fulltext,ref_or_null,unique_subquery,index_subquery,range,index_merge,index,ALL;

  • 除了all之外,其他的type都可以使用到索引;

  • 除了index_merge之外,其他的type只可以用到一个索引;一般来说,好的sql查询至少达到range级别,最好能达到ref。

2.5 possible_keys 列

查询中可能使用到的索引。

2.6 key 列

查询真正使用到的索引。

特殊说明:

select_type为index_merge时,这里可能出现两个以上的索引;其他的select_type这里只会出现一个。

2.7 key_len 列

使用到的索引长度。

说明:

  • 如果是多列索引,查询时不一定使用到所有的列,会计算出具体使用的长度;

  • 只计算where条件用到的索引长度,排序和分组就算用到索引也不会计算到key_len中;

  • 该值越小越好。

2.8 ref 列

表示连接查询的连接条件。

说明:

  • 如果是使用的常数等值查询,这里会显示const;

  • 如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段;

  • 如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func。

2.9 rows 列

表示此次查询预估读取的行数。该值越小越好。

2.10 extra 列

表示解决查询的其他信息,有几十种不同的值,罗列几个常用:

条件 说明
no tables used 查询语句中不含from,或from dual 查询
NULL 询的列未被索引覆盖,并且where筛选条件是索引的前导列 意味着用到了索引,但是部分字段未被索引覆盖,必须通过“回表”来实现,不是纯粹地用到了索引,也不是完全没用到索引
using index 查询时不需要回表查询,直接通过索引就可以获取查询的数据 理想中的SQL查询
Using where 查询的列未被索引覆盖,where筛选条件非索引的前导列
Using where Using index 查询的列被索引覆盖,并且where筛选条件是索引列之一但是不是索引的前导列 意味着无法直接通过索引查找来查询到符合条件的数据
Using index condition 与Using where类似,查询的列不完全被索引覆盖,where条件中是一个前导列的范围
using temporary 表示使用了临时表存储中间结果 临时表可以是内存临时表和磁盘临时表,执行计划中看不出来,需要查看status变量,used_tmp_table,used_tmp_disk_table才能看出来
using filesort mysql 会对结果使用一个外部索引排序,而不是按索引次序从表里读取行 此时mysql会根据连接类型浏览所有符合条件的记录,并保存排序关键字和行指针,然后排序关键字并按顺序检索行信息。这种情况下一般也是要考虑使用索引来优化的
using intersect 表示使用and的各个索引的条件时,该信息表示是从处理结果获取交集
using union 表示使用or连接各个使用索引的条件时,该信息表示从处理结果获取并集
using sort_union
用and和or查询信息量大时,先查询主键,然后进行排序合并后返回结果集

using sort_intersection
firstmatch(tb_name) 可能会在where语句含有in()类型的子查询,若内表的数据量较大时出现 5.6.x版本开始引入的优化子查询的新特性之一
loosescan(m..n) 可能会在in()类型的子查询中,返回记录有重复时出现 5.6.x版本开始引入的优化子查询的新特性

2.11 filtered 列

表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例(不是具体记录数)。

5.7之后版本的默认字段,之前的版本需要使用explain extended时才会出现。


结合这些字段值,我们可以验证SQL的执行效率,也可以作为优化的切入点。