使用数据库时,往往需要针对业务场景编写 SQL 查询语句,以及进行一系列的优化,以提高查询语句的执行效率和执行速度,这时候,需要知道这些 SQL 语句在数据库中的执行过程,来确定查询语句的编写方法,以及评估优化的方向和方法。

MySQL 数据库提供了 EXPLAIN 语句,使用户在真正执行 SQL 查询语句之前,能直观看到本次查询的整个执行过程。在解释 EXPLAIN 语句之前,先来看一下 MySQL 的架构:

在 MySQL Server 中首先有一个 Cache,用来缓存 SQL 查询语句的查询结果,如果缓存命中,则直接返回结果,如果缓存没有命中,则对 SQL 语句进行语法分析,预处理和查询优化,最终得到该查询的执行计划,之后,该执行计划被送往数据库引擎,得到最终查询到的数据。数据库引擎并不会严格按照执行计划进行执行,而是会根据自身的架构和特性进行一些调整,以提高执行效率。

EXPLAIN 语句将生成的执行计划返回给用户,虽然执行计划和查询的实际执行过程并不完全吻合,但差异不大,一定程度上能反映当前查询的执行过程。因此,通过执行计划,我们能够更有针对性地优化 SQL 查询语句。

以下是 EXPLAIN 语句的执行结果:

EXPLAIN 语句共产生了 10 列数据:

id:id 字段用来表明查询的顺序,如果 id 相同,则执行顺序按表中执行顺序自上而下执行,如果 id 不同,id 大的最先执行。图中,id 为 2 语句在 id 为 1 的语句之前执行。

select_type:该字段用来表明查询子句的类型:

SIMPLE:简单查询,没有使用 UNION 或者子查询

PRIMARY:最外层的 SELECT

UNION:UNION 查询中第二个及后续查询语句

SUBQUERY:子查询中的第一个 SELECT

DERIVED:附加表,FROM 子句中的子查询

table:当前输出行操作的表,可以是数据库表,也可能是临时表

type:JOIN 类型,表示多张表 JOIN 查询如何连接生成最终的结果集,常见的类型如下:

ALL,index,range,ref,eq_ref,const,system,NULL

从左至右,查询所需遍历的行数递减。

ALL:MySQL会遍历整个表,以找到所匹配的行

index:MySQL只遍历整个索引树,由于只读取索引树,不需要读取数据块,因此速度比 ALL 稍快

range:MySQL只遍历索引树的一部分,当 WHERE 条件中有 BETWEEN,> 或 < 时,会出现这种 type

ref:扫描非唯一索引,只需要扫描非唯一索引,即可得到匹配的行,使用唯一索引的非唯一前缀进行查询时会出现这种 type

eq_ref:扫描唯一索引,对于前表的每一行,只有唯一的一行与之对应

const:优先查询的表中最多只有一行会被取出,在后续查询中,MySQL 会缓存该查询结果

possible_keys:可能会用到的索引,如果某个索引中包含查询中的字段,则该索引会出现在这个地方。该字段的值并不反映实际使用的索引

key:实际使用到的索引名称

key_len:索引长度,该值表示实际使用的索引的长度,如果使用到了联合索引中的一部分,则使用到的部分会被算在 key_len 中,但未使用到的部分不会计算在 key_len 中。

ref:表示实际使用到的索引所包含的字段名

rows:需要扫描的行数,该行数为一个估计值,并不是准确值,但通过该估值,我们能判断一次查询大致需要扫描多少行,能为查询的优化提供一个参考

extra:查询的额外信息,常见的如下:

using where:使用了 WHERE 语句来约束匹配的行或需要被送到客户端的行

using index:表示查询中使用的字段来自于同一个索引。该值并不表示查询使用了索引

using temporary:查询中创建了临时表

using filesort:MySQL 需要对数据进行额外的扫描,以对数据进行排序。对查询字段存在非排序字段,对非索引字段进行排序等会产生该额外信息

EXPLAIN 语句给出的执行计划并不是实际执行流程,实际执行时,还会基于当前数据的分布和统计,对执行计划进行再一次的优化,例如,数据量的大小,查询使用的索引的区分度,缓存,对于索引的使用和执行流程会有比较大的影响,因此,有时会 EXPLAIN 的输出中出现全表扫描,但实际查询速度却很快,或者出现 EXPLAIN 的输出比较理想,而实际查询执行的却很慢的情况。