MySQL8.0之执行计划

一、SQL执行过程

1.1 SQL语句内部执行过程

  MySQL分为Server层和存储引擎层两部分。Server层包括连接器、分析器、优化器、执行器等等,而存储引擎层负责数据的存储和读取。
  SQL执行时,会通过连接器建立连接、获取权限;连接器会维持和管理连接。
  然后,MySQL会通过分析器对SQL语句进行解析,分析语句各部分含义,然后按照语法规则判断SQL是否符合MySQL的语法。
  经过分析器分析后,MySQL会对SQL请求进行优化器的处理,优化器对语句索引、连接顺序等情况判断,决定使用哪种执行方案最合适。
  最后,就到了执行器的阶段,执行器根据表的引擎定义,去调用引擎接口,执行SQL语句。

1.2 SQL语句执行顺序

1、FROM #对FROM的左边的表和右边的表计算笛卡尔积,产生虚拟表VT1。
2、ON #对虚拟表VT1进行ON筛选,只有那些符合条件的行才会被记录在虚拟表VT2中。
3、JOIN #如果指定了外连接(比如left join、right join),那么保留表中未匹配的行就会作为外部行添加到虚拟表VT2中,产生虚拟表VT3。from子句中包含两个以上的表的话,那么就会对上一个join连接产生的结果VT3和下一个表重复执行步骤1~3这三个步骤,一直到处理完所有的表为止。
4、WHERE #对虚拟表VT3进行WHERE条件过滤。只有符合条件的记录才会被插入到虚拟表VT4中。
5、GROUP BY #根据group by子句中的列,对VT4中的记录进行分组操作,产生虚拟表VT5。
6、AVG, SUM … #对虚拟表VT5进行AVG或者SUM操作,产生虚拟表VT6。
7、HAVING #对虚拟表VT6应用having过滤,只有符合的记录才会被插入到虚拟表VT7中。
8、SELECT #执行select操作,选择指定的列,插入到虚拟表VT8中。
9、DISTINCT #对VT8中的记录进行去重。产生虚拟表VT9。
10、ORDER BY #将虚拟表VT9中的记录按照指定列进行排序操作,产生虚拟表VT10。
11、LIMIT #取出指定行的记录,产生虚拟表VT11, 并将结果返回。

二、执行计划解读

2.1如何查看SQL语句执行计划

  MySQL内置EXPLAIN命令来查看SQL语句的执行计划,EXPLAIN支持SELECT、DELETE、INSERT、REPLACE和UPDATE等语句,也支持对分区表的解析。在MySQL8.0.17中EXPLAIN不在支持EXPLAIN PARTITIONSEXPLAIN EXTENDED语法,并且FORMAT新增TREE格式。通过EXPLAIN展示的信息我们可以了解到表查询的顺序,表连接的方式等,并根据这些信息判断语句执行效率,决定是否添加索引或改写SQL语句优化表连接方式以提高执行效率。
  EXPLAIN语法如下:

{EXPLAIN | DESCRIBE | DESC}
    tbl_name [col_name | wild]

{EXPLAIN | DESCRIBE | DESC}
    [explain_type]
    {explainable_stmt | FOR CONNECTION connection_id}

explain_type: {
    FORMAT = format_name
}

format_name: {
    TRADITIONAL
  | JSON
  | TREE
}

explainable_stmt: {
    SELECT statement
  | DELETE statement
  | INSERT statement
  | REPLACE statement
  | UPDATE statement
}
DESCRIBE和EXPLAIN语句是同义词。但DESCRIBE关键字更常用来获取关于表的信息结构,而EXPLAIN用于获取查询执行计划。

  本文将对EXPLAIN的三种格式的执行计划输出进行讲解:

2.2 TRADITIONAL格式输出说明

  RADITIONAL格式也就是默认格式,输出格式如下:

mysql> explain format='TRADITIONAL' select t1.id,t1.address,t2.from_date,t3.dept_name from t1 left join t2 on t1.id=t2.id left join t3 on t2.dept_id=t3.dept_id where t3.dept_name in (select dept_name from t3 whhere dept_id<1000)
+----+-------------+-------+------------+--------+-----------------------+---------------+---------+-----------------+-------+----------+--------------------------+
| id | select_type | table | partitions | type   | possible_keys         | key           | key_len | ref             | rows  | filtered | Extra                    |
+----+-------------+-------+------------+--------+-----------------------+---------------+---------+-----------------+-------+----------+--------------------------+
|  1 | PRIMARY     | t1    | NULL       | index  | NULL                  | idx_address   | 202     | NULL            | 99975 |   100.00 | Using index              |
|  1 | PRIMARY     | t2    | NULL       | eq_ref | PRIMARY               | PRIMARY       | 4       | test.t1.id      |     1 |   100.00 | NULL                     |
|  1 | PRIMARY     | t3    | NULL       | eq_ref | PRIMARY               | PRIMARY       | 16      | test.t2.dept_id |     1 |   100.00 | Using where              |
|  2 | SUBQUERY    | t3    | NULL       | index  | PRIMARY,idx_dept_name | idx_dept_name | 202     | NULL            | 81406 |    33.33 | Using where; Using index |
+----+-------------+-------+------------+--------+-----------------------+---------------+---------+-----------------+-------+----------+--------------------------+
4 rows in set, 2 warnings (0.01 sec)

  各字段含义如下:

字段名含义
id标识符,语句涉及表的执行顺序
select_type表查询类型
table表名称
partitions涉及表哪个分区
type表的查询(连接)类型
possible_keys表可能使用到的索引
key表实际使用到的索引
key_len表实际使用索引的长度,单位:字节
ref表哪些字段或者常量用于连接查找索引上的值
rows查询预估返回表的行数
filtered表经过条件过滤之后与总数的百分比
Extra额外的说明信息
2.2.1 id

  id值越小越为查询的外部,越大越为查询的内部。id值按照由大到小的顺序执行,如果id值相同,自上而下执行。

2.2.2 select_type
select_type值含义
SIMPLE简单查询,不包含unino查询或子查询
PRIMARY位于最外部的查询
UNION当出现union查询时第二个或之后的查询
DEPENDENT UNION当出现union查询时第二个或之后的查询,取决于外部查询
UNION RESULTunion查询的结果集
SUBQUERY子查询当中第一个select查询
DEPENDENT SUBQUERY子查询当中第一个select查询,取决于外部的查询
DERIVED衍生表(FROM子句中的子查询)
MATERIALIZED物化子查询
UNCACHEABLE SUBQUERY结果集无法缓存的子查询,必须重新评估外部查询的每一行
UNCACHEABLE UNIONUNION中第二个或之后的SELECT,属于无法缓存的子查询
2.2.3 table

  当前是从哪张表获取数据,如果为表指定了别名,则显示别名,如果没有涉及对表的数据读取,则显示NULL,还有如下几种情形:

<unionM,N>: 引用id为M和N UNION后的结果。
<derivedN>: 引用id为N的结果派生出的表。派生表可以是一个结果集,例如派生自FROM中子查询的结果。
<subqueryN>: 引用id为N的子查询结果物化得到的表。即生成一个临时表保存子查询的结果。
2.2.4 partitions

  该列显示的为分区表命中的分区情况。非分区表该字段为空(null)。

2.2.5 type

  按照最好到最差的连接类型依次为:system,const,eq_ref,ref,fulltext,ref_or_null,index_merge,unique_subquery,index_subquery,range,index,ALL。
  除了ALL之外,其他的type都可以使用到索引,除了index_merge之外,其他的type只可以用到一个索引。

  • system:表中只有一行数据或者是空表,这是const类型的一个特例。且只能用于myisam和memory表。如果是Innodb引擎表,type列在这个情况通常都是all或者index。
  • const:最多只有一行记录匹配。当联合主键或唯一索引的所有字段跟常量值比较时,join类型为const。其他数据库也叫做唯一索引扫描。
  • eq_ref:多表join时,对于来自前面表的每一行,在当前表中只能找到一行。这是除了system和const之外最好的类型。当主键或唯一非NULL索引的所有字段都被用作join联接时会使用此类型。eq_ref可用于使用’='操作符作比较的索引列。比较的值可以是常量,也可以是使用在此表之前读取的表的列的表达式。
  • ref:对于来自前面表的每一行,在此表的索引中可以匹配到多行。若联接只用到索引的最左前缀或索引不是主键或唯一索引时,使用ref类型(也就是说,此联接能够匹配多行记录)。ref可用于使用’=‘或’<=>'操作符作比较的索引列。
eq_ref相对于ref区别就是它使用的唯一索引,即主键或唯一索引,而ref使用的是非唯一索引或者普通索引。eq_ref只能找到一行,而ref能找到多行。
  • fulltext:使用全文索引的时候是这个类型。要注意,全文索引的优先级很高,若全文索引和普通索引同时存在时,mysql不管代价,优先选择使用全文索引
  • ref_or_null:跟ref类型类似,只是增加了null值的比较。实际用的不多。
  • index_merge:表示查询使用了两个以上的索引,最后取交集或者并集,常见and,or的条件使用了不同的索引,官方排序这个在ref_or_null之后,但是实际上由于要读取多个索引,性能可能大部分时间都不如range
  • unique_subquery:用于where中的in形式子查询,子查询返回不重复值唯一值,可以完全替换子查询,效率更高。
  • index_subquery:该联接类型类似于unique_subquery。适用于非唯一索引,可以返回重复值。
  • range:索引范围查询,常见于使用 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN()或者like等运算符的查询中。
  • index:索引全表扫描,把索引从头到尾扫一遍。这里包含两种情况:
    一种是查询使用了覆盖索引,那么它只需要扫描索引就可以获得数据,这个效率要比全表扫描要快,因为索引通常比数据表小,而且还能避免二次查询。在extra中显示Using index,反之,如果在索引上进行全表扫描,没有Using index的提示。
  • ALL:全表扫描,性能最差。
2.2.6 possible_keys

  显示了MySQL在查找当前表中数据的时候可能使用到的索引,实际意义不大。

2.2.7 key

  显示了MySQL在实际查找数据时决定使用的索引,如果该字段值为NULL,则表明没有使用索引。

2.2.8 key_len

  显示了MySQL实际使用索引的大小,单位字节。可以通过key_len的大小判断评估复合索引使用了哪些部分。
几种常见字段类型索引长度大小如下,假设字符编码为utf8mb4:如果字段允许为NULL,则需要额外增加一个字节;
字符型:
char(n):4n个字节
varchar(n):4n+2个字节
数值型:
tinyint:1个字节
int:4个字节
bigint:8个字节
时间型:
date:3个字节
datetime:5个字节+秒精度字节
timestamp:4个字节+秒精度字节
秒精度字节(最大6位):
1~2位:1个字节
3~4位:2个字节
5~6位:3个字节

2.2.9 ref

  如果是使用的常数等值查询,这里会显示const,如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段,如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func。

2.2.10 rows

  这是mysql估算的需要扫描的行数(不是精确值)。这个值非常直观显示 SQL的效率好坏, 原则上rows越少越好。

2.2.11 filtered

  这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。

2.2.12 Extra

  EXPLAIN中的很多额外的信息会在 Extra 字段显示,常见的有以下几种内容:

  • Using index
      仅查询索引树就可以获取到所需要的数据行,而不需要读取表中实际的数据行。通常适用于select字段就是查询使用索引的一部分,即使用了覆盖索引。
mysql> explain select dept_id from t2;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | t2    | NULL       | index | NULL          | idx_dept_id | 16      | NULL | 100035 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
  • Using index condition
      显示采用了Index Condition Pushdown(ICP)特性通过索引去表中获取数据。关于ICP特性可以参考官方文档:Index Condition Pushdown Optimization。简单说法如下:
      如果开启ICP特性,部分where条件部分可以下推到存储引擎通过索引进行过滤,ICP可以减少存储引擎访问基表的次数;
      如果没有开启ICP特性,则存储引擎根据索引需要直接访问基表获取数据并返回给server层进行where条件的过滤。
#set persist optimizer_switch='index_condition_pushdown=off';
mysql> explain select * from t2  where to_date='1980-01-01' and from_date<'1970-01-01';                                                                                                                          
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                  |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+------------------------+
|  1 | SIMPLE      | t2    | NULL       | range | idx_from_to   | idx_from_to | 5       | NULL |    1 |    10.00 | Using where; Using MRR |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+------------------------+
1 row in set, 1 warning (0.00 sec)
#set persist optimizer_switch='index_condition_pushdown=on';
mysql> explain select * from t2  where to_date='1980-01-01' and from_date<'1970-01-01';
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+----------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                            |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+----------------------------------+
|  1 | SIMPLE      | t2    | NULL       | range | idx_from_to   | idx_from_to | 5       | NULL |    1 |    10.00 | Using index condition; Using MRR |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+----------------------------------+
1 row in set, 1 warning (0.00 sec)
  • Using where
    显示MySQL通过索引条件定位之后还需要返回表中获得所需要的数据。
mysql> explain select * from t2  where to_date='1980-01-01';
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 100035 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.40 sec)
  • Impossible WHERE
    where子句的条件永远都不可能为真。
mysql> explain select * from t2  where 1=0;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra            |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
|  1 | SIMPLE      | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | Impossible WHERE |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
1 row in set, 1 warning (0.00 sec)
  • Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access)
    在表联接过程当中,将先前表的部分数据读取到join buffer缓冲区中,然后从缓冲区中读取数据与当前表进行连接。主要有两种算法:Block Nested Loop和Batched Key Access。
#开关BKA参数
SET PERSIST optimizer_switch='mrr=on,mrr_cost_based=off,block_nested_loop=on,batched_key_access=on/off';
mysql> explain select * from t1 join t2 on t1.birth_date=t2.to_date;
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 100035 |   100.00 | NULL                                               |
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  99975 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

mysql> explain select * from t2 join t3 on t3.dept_id=t2.dept_id;
+----+-------------+-------+------------+-------+---------------+---------------+---------+-----------------+-------+----------+----------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key           | key_len | ref             | rows  | filtered | Extra                                  |
+----+-------------+-------+------------+-------+---------------+---------------+---------+-----------------+-------+----------+----------------------------------------+
|  1 | SIMPLE      | t3    | NULL       | index | PRIMARY       | idx_dept_name | 202     | NULL            | 81406 |   100.00 | Using index                            |
|  1 | SIMPLE      | t2    | NULL       | ref   | idx_dept_id   | idx_dept_id   | 16      | test.t3.dept_id |     1 |   100.00 | Using join buffer (Batched Key Access) |
+----+-------------+-------+------------+-------+---------------+---------------+---------+-----------------+-------+----------+----------------------------------------+
2 rows in set, 1 warning (0.10 sec)
  • Using MRR
    读取数据采用多范围读(Multi-Range Read)的优化策略。
mysql> explain select * from t2  where to_date='1980-01-01' and from_date<'1970-01-01';                                                                                                                          
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                  |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+------------------------+
|  1 | SIMPLE      | t2    | NULL       | range | idx_from_to   | idx_from_to | 5       | NULL |    1 |    10.00 | Using where; Using MRR |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+------------------------+
1 row in set, 1 warning (0.00 sec)
  • Using temporary
    MySQL需要创建临时表来存放查询结果集。通常发生在有GROUP BY或ORDER BY子句的语句当中。
mysql> explain select * from t1 join t2 on t1.birth_date=t2.from_date  order by t2.id;
+----+-------------+-------+------------+------+---------------+-------------+---------+--------------------+-------+----------+----------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref                | rows  | filtered | Extra                                  |
+----+-------------+-------+------------+------+---------------+-------------+---------+--------------------+-------+----------+----------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL        | NULL    | NULL               | 99975 |   100.00 | Using temporary; Using filesort        |
|  1 | SIMPLE      | t2    | NULL       | ref  | idx_from_to   | idx_from_to | 5       | test.t1.birth_date |     1 |   100.00 | Using join buffer (Batched Key Access) |
+----+-------------+-------+------------+------+---------------+-------------+---------+--------------------+-------+----------+----------------------------------------+
2 rows in set, 1 warning (0.00 sec)
  • Using filesort
    MySQL需要对获取的数据进行额外的一次排序操作,无法通过索引的排序完成。通常发生在有ORDER BY子句的语句当中。
mysql> explain select * from t1 join t2 on t1.birth_date=t2.from_date  order by t2.id;
+----+-------------+-------+------------+------+---------------+-------------+---------+--------------------+-------+----------+----------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref                | rows  | filtered | Extra                                  |
+----+-------------+-------+------------+------+---------------+-------------+---------+--------------------+-------+----------+----------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL        | NULL    | NULL               | 99975 |   100.00 | Using temporary; Using filesort        |
|  1 | SIMPLE      | t2    | NULL       | ref  | idx_from_to   | idx_from_to | 5       | test.t1.birth_date |     1 |   100.00 | Using join buffer (Batched Key Access) |
+----+-------------+-------+------------+------+---------------+-------------+---------+--------------------+-------+----------+----------------------------------------+
2 rows in set, 1 warning (0.00 sec)

2.3 JSON格式输出说明

mysql> explain format='JSON' select t1.id,t1.address,t2.from_date,t3.dept_name from t1 left join t2 on t1.id=t2.id left join t3 on t2.dept_id=t3.dept_id where t3.dept_name in (select dept_name from t3 where deppt_id<1000) or  t2.id<10000;
+------------------------------------------------------------------------------------------------+
| EXPLAIN                                               |
| {
  "query_block": { #整个查询块
    "select_id": 1, #等同于默认格式的id
    "cost_info": { #具体成本信息
      "query_cost": "80068.25" #select_id=1时的成本为80068.25
    },
    "nested_loop": [ #记录SQL执行的类型信息
      {
        "table": {#按照具体表进行解释
          "table_name": "t1", #等同于默认格式的table
          "access_type": "index",#等同于默认格式的type
          "key": "idx_address",#等同于默认格式的key
          "used_key_parts": [ #使用到的具体部分
            "address"
          ],
          "key_length": "202",#等同于默认格式的key_len
          "rows_examined_per_scan": 99975, #每次扫描的行数
          "rows_produced_per_join": 99975, #每次连接的行数
          "filtered": "100.00",#等同于默认格式的filtered
          "using_index": true, #是否使用到索引
          "cost_info": {#此部分的具体成本
            "read_cost": "88.25",#读取的成本
            "eval_cost": "9997.50",#评估的成本
            "prefix_cost": "10085.75",#加入JOIN中下一个表的成本
            "data_read_per_join": "32M"#JOIN操作应读取的数据量
          },
          "used_columns": [#使用到的字段
            "id",
            "address"
          ]
        }
      },
      {
        "table": {
          "table_name": "t2",
          "access_type": "eq_ref",
          "possible_keys": [
            "PRIMARY"
          ],
          "key": "PRIMARY",
          "used_key_parts": [
            "id"
          ],
          "key_length": "4",
          "ref": [ #等同于默认格式的ref
            "test.t1.id"
          ],
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 99975,
          "filtered": "100.00",
          "cost_info": {
            "read_cost": "24993.75",
            "eval_cost": "9997.50",
            "prefix_cost": "45077.00",
            "data_read_per_join": "3M"
          },
          "used_columns": [
            "id",
            "dept_id",
            "from_date"
          ]
        }
      },
      {
        "table": {
          "table_name": "t3",
          "access_type": "eq_ref",
          "possible_keys": [
            "PRIMARY"
          ],
          "key": "PRIMARY",
          "used_key_parts": [
            "dept_id"
          ],
          "key_length": "16",
          "ref": [
            "test.t2.dept_id"
          ],
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 99975,
          "filtered": "100.00",
          "cost_info": {
            "read_cost": "24993.75",
            "eval_cost": "9997.50",
            "prefix_cost": "80068.25",
            "data_read_per_join": "21M"
          },
          "used_columns": [
            "dept_id",
            "dept_name"
          ],
          "attached_condition": "<if>(found_match(t3), (<in_optimizer>(`test`.`t3`.`dept_name`,`test`.`t3`.`dept_name` in ( <materialize> (/* select#2 */ select `test`.`t3`.`dept_name` from `test`.`t3` where (`test`.`t3`.`dept_id` < 1000) ), <primary_index_lookup>(`test`.`t3`.`dept_name` in <temporary table> on <auto_key> where ((`test`.`t3`.`dept_name` = `materialized-subquery`.`dept_name`))))) or (`test`.`t2`.`id` < 10000)), true)",#显示一些附加条件
          "attached_subqueries": [#附加子查询
            {
              "table": {
                "table_name": "<materialized_subquery>",#物化子查询
                "access_type": "eq_ref",
                "key": "<auto_key>",
                "key_length": "202",
                "rows_examined_per_scan": 1,
                "materialized_from_subquery": {
                  "using_temporary_table": true,#使用了临时表
                  "dependent": true,
                  "cacheable": false,#结果无法缓存
                  "query_block": {
                    "select_id": 2,
                    "cost_info": {
                      "query_cost": "8228.85"
                    },
                    "table": {
                      "table_name": "t3",
                      "access_type": "index",
                      "possible_keys": [
                        "PRIMARY",
                        "idx_dept_name"
                      ],
                      "key": "idx_dept_name",
                      "used_key_parts": [
                        "dept_name"
                      ],
                      "key_length": "202",
                      "rows_examined_per_scan": 81406,
                      "rows_produced_per_join": 27132,
                      "filtered": "33.33",
                      "using_index": true,
                      "cost_info": {
                        "read_cost": "5515.59",
                        "eval_cost": "2713.26",
                        "prefix_cost": "8228.85",
                        "data_read_per_join": "5M"
                      },
                      "used_columns": [
                        "dept_id",
                        "dept_name"
                      ],
                      "attached_condition": "(`test`.`t3`.`dept_id` < 1000)"
                    }
                  }
                }
              }
            }
          ]
        }
      }
    ]
  }
} |
+------------------------------------------------------------------------------------------------+
1 row in set, 2 warnings (0.01 sec)

2.4 TREE格式输出说明

mysql> explain format='TREE' select t1.id,t1.address,t2.from_date,t3.dept_name from t1 left join t2 on t1.id=t2.id left join t3 on t2.dept_id=t3.dept_id where t3.dept_name in (select dept_name from t3 where deppt_id<1000) or  t2.id<10000;
+------------------------------------------------------------------------------------------------+
| EXPLAIN                |
+------------------------------------------------------------------------------------------------+
| -> Filter: (<in_optimizer>(t3.dept_name,t3.dept_name in (select #2)) or (t2.id < 10000))
    -> Nested loop left join
        -> Nested loop left join #连接方式
            -> Index scan on t1 using idx_address #使用到的索引
            -> Single-row index lookup on t2 using PRIMARY (id=t1.id) #具体使用的索引相关信息
        -> Single-row index lookup on t3 using PRIMARY (dept_id=t2.dept_id)
    -> Select #2 (subquery in condition; run only once)#子查询;只运行一次
        -> Filter: (t3.dept_id < 1000)#条件信息
            -> Index scan on t3 using idx_dept_name
 |
+------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (0.11 sec)

三、总结

  本文对MySQL8.0中SQL的内部执行流程、SQL执行顺序以及执行计划的三种格式分别进行了讲述,其他MySQL8.0的新特性将在以后的博客中陆续展出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_梓杰_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值