百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

你会看 MySQL 的执行计划(EXPLAIN)吗?

wptr33 2025-09-13 10:32 56 浏览

SQL 执行太慢怎么办?我们通常会使用 EXPLAIN 命令来查看 SQL 的执行计划,然后根据执行计划找出问题所在并进行优化。

用法简介

EXPLAIN 的用法很简单,只需要在你的 SQL 前面加上 EXPLAIN 即可。例如:

 explain select * from t;

PS:insert、update、delete 同样可以通过 explain 查看执行计划,不过通常我们更关心 select 的执行情况

你会看到如下输出:

 +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
 | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
 +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
 |  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | NULL  |
 +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
 1 row in set, 1 warning (0.00 sec)

执行计划结果字段说明如下表:

字段

JSON Name

说明

id

select_id

查询标识符

select_type

/

查询类型

table

table_name

查询记录所在表

partitions

partitions

查询匹配的分区(没有进行表分区,则为 NULL)

type

access_type

连接类型

possible_keys

possible_keys

可以选择的索引

key

key

实际上用到的索引

key_len

key_length

被用到索引的长度,比如联合索引中有几个被用到

ref

ref

与索引相比较的列

rows

rows

要扫描的行数(估算值)

filtered

filtered

按表条件过滤的行百分比

Extra

/

附加信息

EXPLAIN 的用法非常简单,看一眼就会。但是要根据输出结果找到问题并解决,就没那么容易了。就好比操作拍 CT 的机器可能相对简单,但要从 CT 成像中看出问题并给出治疗方案就需要丰富的知识和大量的临床经验了。

因此,我们需要知道每个字段代表什么指标;什么样的取值是我们想要的,什么样是需要优化的;最后还要知道如何优化成我们想要的值。

字段详解

id

标识符。查询操作的序列号。通常都是正整数,但当有 UNION 操作时,该值可以为 NULL。

id 相同

 explain select * from t1 where t1.id in (select t2.id from t2);
 +----+-------------+-------+------------+--------+---------------+--------+
 | id | select_type | table | partitions | type   | possible_keys | ...    |
 +----+-------------+-------+------------+--------+---------------+--------+
 |  1 | SIMPLE      | t1    | NULL       | ALL    | PRIMARY       | ....   |
 |  1 | SIMPLE      | t2    | NULL       | eq_ref | PRIMARY       | ....   |
 +----+-------------+-------+------------+--------+---------------+--------+
 2 rows in set, 1 warning (0.00 sec)

id 不同

 explain select * from t1 where t1.id = (select t2.id from t2);
 +----+-------------+-------+------------+-------+---------------+--------+
 | id | select_type | table | partitions | type  | possible_keys | ...    |
 +----+-------------+-------+------------+-------+---------------+--------+
 |  1 | PRIMARY     | NULL  | NULL       | NULL  | NULL          | ....   |
 |  2 | SUBQUERY    | t2    | NULL       | index | NULL          | ....   |
 +----+-------------+-------+------------+-------+---------------+--------+
 2 rows in set, 1 warning (0.00 sec)

id 包含 NULL

 explain select id from t1 union (select id from t2);
 +----+--------------+------------+------------+-------+---------------+-----------+
 | id   | select_type  | table      | partitions | type  | possible_keys | ...     |
 +------+--------------+------------+------------+-------+---------------+---------+
 |  1   | PRIMARY      | t1         | NULL       | index | NULL          | ...     |
 |  2   | UNION        | t2         | NULL       | index | NULL          | ...     |
 | NULL | UNION RESULT | <union1,2> | NULL       | ALL   | NULL          | ...     |
 +------+--------------+------------+------------+-------+---------------+---------+
 3 rows in set, 1 warning (0.00 sec)

id 为 NULL 时,table 列值为 < unionM,n > 格式,表示该行为 id 为 m 和 n 联合的结果

id 顺序的规则:如果 id 相同,执行顺序由上到下;如果不同,执行顺序由大到小。

select_type

SELECT 类型,常见的取值如下表:

查询类型

JSON Name

说明

SIMPLE

/

简单 SELECT(没有 UNION 或子查询)

PRIMARY

/

查询包含 UNION 或子查询,则最外层的查询被标识为 PRIMARY

UNION

/

UNION 中的第二个或更后面的 SELECT 语句

DEPENDENT UNION

dependent (true)

UNION 中的第二个或更后面的 SELECT 语句,依赖于外部查询

UNION RESULT

union_result

UNION 的结果

SUBQUERY

/

子查询中的第一个 SELECT 语句

DEPENDENT SUBQUERY

dependent (true)

子查询中的第一个 SELECT 语句,依赖于外部查询

DERIVED

/

派生表 SELECT

DEPENDENT DERIVED

dependent (true)

派生表依赖于另一个表

MATERIALIZED

materialized_from_subquery

将子查询的结果物化(生成临时表)

UNCACHEABLE SUBQUERY

cacheable (false)

结果无法缓存且必须为外部查询的每一行重新计算的子查询

UNCACHEABLE UNION

cacheable (false)

UNION 中的第二个或更后面的 SELECT 语句,属于不可缓存子查询(参考 UNCACHEABLE SUBQUERY)

UNION 或者子查询 MySQL 会自动产生临时表。派生表可以简单理解为具有别名的临时表。生成临时表的这个动作称为物化(水变成蒸汽叫汽化)

临时表通常在内存里,当其 size 超过一定范围会被存入磁盘

 # 临时表
  select * from t1 join t2 on t1.id = t2.id where t1.id > 1;
 
 # 派生表,临时表取个别名
 select * from (select * from t1) t;

type

连接字段为主键或者唯一索引,此类型通常出现于多表的join查询,表示对于前表的每一个结果,都对应后表的唯一一条结果。并且查询的比较是=操作,查询效率比较高。

取值

说明

system

表中只有一条记录,const 类型的特例

const

表中最多有一条匹配数据,用于主键或唯一索引的等值匹配

eq_ref

出现在多表查询中,前表结果中的每一条记录,在后表中有唯一的对应。同样是主键或唯一索引等值匹配

ref

普通索引的等值匹配(= 或 <=>)

fulltext

全文索引

ref_or_null

跟 ref 类似,增加了对 NULL 的判断

index_merge

合并索引(用到了两个及以上的索引)

unique_subquery

子查询用到主键或唯一索引

index_subquery

子查询用到普通索引

range

范围匹配( = 、 < > 、 > 、 > = 、 < 、 < = 、 IS NULL、 < = > 、 BETWEEN、 LIKE 或 IN)

index

扫描索引树(在覆盖索引的情况下优于 ALL)

ALL

全表扫描

还有一种 NULL 的情况,比如 select min(id) from t1,但 MySQL 官方没有提及这种情况,所以我们不在此讨论

性能从优到劣依次为:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

优化原则:最好做到 const,至少做到 ref,避免 ALL

ref

查询中用来和索引比较的类型,如:id = 1,值为 const;如果是联合查询或者子查询则为关联的字段;如果使用了函数,则为 func。

Extra

Extra 用来存放一些附加信息,通常用来配合 type 的输出来做 SQL 优化。

扩展

desc

desc 与 explain 作用相同,可以互相代替,后面的例子中均使用 desc 来查看执行计划。

format

explain/desc 还支持一些参数,format 顾名思义,是用来格式化输出结果的。它包括两种格式化方式:tree 和 json。

比如:

 desc format = tree select * from t1 where t1.id in (select t2.id from t2 where t2.id > 1);

输出格式如下:

 +----------------------------------------------------------------------------------+
 | EXPLAIN                                                                          |
 +----------------------------------------------------------------------------------+
 | -> Nested loop inner join  (cost=0.70 rows=1)
     -> Filter: (t2.id > 1)  (cost=0.35 rows=1)
         -> Index scan on t2 using a2_uidx  (cost=0.35 rows=1)
     -> Single-row index lookup on t1 using PRIMARY (id=t2.id)  (cost=0.35 rows=1)
  |
 +----------------------------------------------------------------------------------+
 1 row in set (0.00 sec)

执行计划结果以树形结构展示,可以清晰的看出语句之间的嵌套关系,还有基本的执行成本(cost)。

使用 json 方式:

 desc format = json select * from t1;

输出结构为一个 JSON 结构:

 +---------------------------------------------------+
 | EXPLAIN                                           |
 +---------------------------------------------------+
 | {
   "query_block": {
     "select_id": 1,
     "cost_info": {
       "query_cost": "0.35"
     },
     "table": {
       "table_name": "t1",
       "access_type": "ALL",
       "rows_examined_per_scan": 1,
       "rows_produced_per_join": 1,
       "filtered": "100.00",
       "cost_info": {
         "read_cost": "0.25",
         "eval_cost": "0.10",
         "prefix_cost": "0.35",
         "data_read_per_join": "56"
       },
       "used_columns": [
         "id",
         "a1",
         "b1"
       ]
     }
   }
 } |
 +---------------------------------------------------+
 1 row in set, 1 warning (0.00 sec)

简介表中的 JSON Name 指的就是这里 JSON 结果的 key

json 格式会展示出更加详细的信息,可以看到执行成本划分的更加细致了,方便定位到慢 SQL 的问题具体出现在哪个环节。

analyze

除了 format 以外,explain/desc 还可以使用 analyze 参数:

 desc analyze select * from t1 where t1.id in (select t2.id from t2 where t2.id > 1);

输出结果:

 +-------------------------------------------------------------------------------------------------------+
 | EXPLAIN                                                                                               |
 +-------------------------------------------------------------------------------------------------------+
 | -> Nested loop inner join  (cost=0.70 rows=1) (actual time=0.018..0.018 rows=0 loops=1)
     -> Filter: (t2.id > 1)  (cost=0.35 rows=1) (actual time=0.016..0.016 rows=0 loops=1)
         -> Index scan on t2 using a2_uidx  (cost=0.35 rows=1) (actual time=0.015..0.015 rows=0 loops=1)
     -> Single-row index lookup on t1 using PRIMARY (id=t2.id)  (cost=0.35 rows=1) (never executed)
  |
 +-------------------------------------------------------------------------------------------------------+
 1 row in set (0.00 sec)

可以看出,analyze 的输出结果是基于 format = tree 的

上面执行计划中(format = json/tree)的执行成本(cost)都是估值,而 analyze 中的执行成本是真实值。actual time 代表对应 SQL 执行的真实时间,单位为毫秒。

最后

执行计划的结果中,我们最关心的是 type,它能够最直接的反映出 SQL 执行效率处在什么级别。然后再结合其他字段(例如 Extra)来做更细致的分析。还可以通过各种参数,来分解每个环节的执行情况。

今天的内容就到这里,有哪些想要了解的可以留言告诉我。

相关推荐

oracle数据导入导出_oracle数据导入导出工具

关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...

继续学习Python中的while true/break语句

上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...

python continue和break的区别_python中break语句和continue语句的区别

python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...

简单学Python——关键字6——break和continue

Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...

2-1,0基础学Python之 break退出循环、 continue继续循环 多重循

用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...

Python 中 break 和 continue 傻傻分不清

大家好啊,我是大田。...

python中的流程控制语句:continue、break 和 return使用方法

Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...

L017:continue和break - 教程文案

continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...

作为前端开发者,你都经历过怎样的面试?

已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...

面试被问 const 是否不可变?这样回答才显功底

作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...

2023金九银十必看前端面试题!2w字精品!

导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...

前端面试总结_前端面试题整理

记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...

由浅入深,66条JavaScript面试知识点(七)

作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...

2024前端面试真题之—VUE篇_前端面试题vue2020及答案

添加图片注释,不超过140字(可选)...

今年最常见的前端面试题,你会做几道?

在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...