Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 Reference Manual  /  ...  /  EXPLAIN Statement

15.8.2 解释语句

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

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

{EXPLAIN | DESCRIBE | DESC} ANALYZE [FORMAT = TREE] [schema_spec] select_statement

explain_type: {
    FORMAT = format_name
}

format_name: {
    TRADITIONAL
  | JSON
  | TREE
}

explainable_stmt: {
    SELECT statement
  | TABLE statement
  | DELETE statement
  | INSERT statement
  | REPLACE statement
  | UPDATE statement
}

schema_spec:
FOR {SCHEMA | DATABASE} schema_name

DESCRIBE 和 EXPLAIN 语句是同义词。在实际应用中,使用 DESCRIBE 关键字来获取表结构信息,而使用 EXPLAIN 关键字来获取查询执行计划(即 MySQL 将如何执行查询的解释)。

以下讨论将使用 DESCRIBE 和 EXPLAIN 关键字,但 MySQL 解析器将它们视为完全同义词。

DESCRIBE 提供关于表中的列信息:

mysql> DESCRIBE City;
+------------+----------+------+-----+---------+----------------+
| Field      | Type     | Null | Key | Default | Extra          |
+------------+----------+------+-----+---------+----------------+
| Id         | int(11)  | NO   | PRI | NULL    | auto_increment |
| Name       | char(35) | NO   |     |         |                |
| Country    | char(3)  | NO   | UNI |         |                |
| District   | char(20) | YES  | MUL |         |                |
| Population | int(11)  | NO   |     | 0       |                |
+------------+----------+------+-----+---------+----------------+

DESCRIBESHOW COLUMNS 的简写。这些语句也显示视图的信息。DESCRIBE 语句的描述提供了关于输出列的更多信息。见第15.7.7.6节,“SHOW COLUMNS 语句”

默认情况下,DESCRIBE 显示表中的所有列信息。col_name,如果给出,是表中的一个列名。在这种情况下,语句只显示该列的信息。wild,如果给出,是一个模式字符串。它可以包含 SQL 的%_ 通配符字符。在这种情况下,语句只显示名称匹配该字符串的列没有必要将字符串括在引号中,除非它包含空格或其他特殊字符。

描述语句是为了与 Oracle 兼容提供的。

SHOW CREATE TABLESHOW TABLE STATUSSHOW INDEX 语句也提供了表的信息。见第15.7.7节,“SHOW 语句”

explain_format 系统变量对 EXPLAIN 语句的输出无效,用于获取表列信息。

EXPLAIN 语句提供了 MySQL 执行语句的信息:

  • EXPLAINSELECTDELETEINSERTREPLACEUPDATETABLE 语句一起工作。

  • EXPLAIN 语句与可解释的语句一起使用时,MySQL 显示优化器关于语句执行计划的信息,即 MySQL 解释如何处理语句,包括表的连接顺序等。关于使用 EXPLAIN 获得执行计划信息,请参见第10.8.2节,“EXPLAIN 输出格式”

  • EXPLAIN 语句与 FOR CONNECTION connection_id 一起使用,而不是可解释的语句,它将显示在命名连接中执行语句的执行计划。请参见第10.8.4节,“获取命名连接的执行计划信息”

  • 对于可解释语句,EXPLAIN 语句还可以生成额外的执行计划信息,可以使用SHOW WARNINGS 显示。请参见第10.8.3节,“扩展 EXPLAIN 输出格式”

  • EXPLAIN 对于涉及分区表的查询非常有用。见第26.3.5节,“获取分区信息”

  • FORMAT 选项可以选择输出格式。TRADITIONAL 使用表格格式输出。这是默认情况,如果没有FORMAT 选项。JSON 格式以 JSON 格式显示信息。TREE 提供树形输出,具有更准确的查询处理描述;它是唯一一个显示哈希连接使用(见第10.2.1.4节,“哈希连接优化”)的格式,并且总是用于EXPLAIN ANALYZE

    在 MySQL 8.4 中,EXPLAIN 的默认输出格式(即没有FORMAT 选项)由explain_format 系统变量确定。该变量的精确效果在本节后面描述。

    MySQL 8.4 支持额外的INTO 选项与EXPLAIN FORMAT=JSON,使得将 JSON 格式输出保存到用户变量中,如下所示:

    mysql> EXPLAIN FORMAT=JSON INTO @myselect 
        ->     SELECT name FROM a WHERE id = 2;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SELECT @myselect\G
    *************************** 1. row ***************************
    @myex: {
      "query_block": {
        "select_id": 1,
        "cost_info": {
          "query_cost": "1.00"
        },
        "table": {
          "table_name": "a",
          "access_type": "const",
          "possible_keys": [
            "PRIMARY"
          ],
          "key": "PRIMARY",
          "used_key_parts": [
            "id"
          ],
          "key_length": "4",
          "ref": [
            "const"
          ],
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 1,
          "filtered": "100.00",
          "cost_info": {
            "read_cost": "0.00",
            "eval_cost": "0.10",
            "prefix_cost": "0.00",
            "data_read_per_join": "408"
          },
          "used_columns": [
            "id",
            "name"
          ]
        }
      }
    }
    1 row in set (0.00 sec)

    这与任何可解释的语句 (SELECT, TABLE, INSERT, UPDATE, REPLACE, 或DELETE) 都可以工作。使用 UPDATEDELETE 语句的示例在这里展示:

    mysql> EXPLAIN FORMAT=JSON INTO @myupdate 
        ->   UPDATE a SET name2 = "garcia" WHERE id = 3;              
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> EXPLAIN FORMAT=JSON INTO @mydelete 
        ->     DELETE FROM a WHERE name1 LIKE '%e%';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SELECT @myupdate, @mydelete\G
    *************************** 1. row ***************************
    @myupdate: {
      "query_block": {
        "select_id": 1,
        "table": {
          "update": true,
          "table_name": "a",
          "access_type": "range",
          "possible_keys": [
            "PRIMARY"
          ],
          "key": "PRIMARY",
          "used_key_parts": [
            "id"
          ],
          "key_length": "4",
          "ref": [
            "const"
          ],
          "rows_examined_per_scan": 1,
          "filtered": "100.00",
          "attached_condition": "(`db`.`a`.`id` = 3)"
        }
      }
    }
    @mydelete: {
      "query_block": {
        "select_id": 1,
        "table": {
          "delete": true,
          "table_name": "a",
          "access_type": "ALL",
          "rows_examined_per_scan": 2,
          "filtered": "100.00",
          "attached_condition": "(`db`.`a`.`name1` like '%e%')"
        }
      }
    }
    1 row in set (0.00 sec)

    您可以使用 MySQL JSON 函数与任何其他 JSON 值一样来处理这个值,例如使用JSON_EXTRACT()

    mysql> SELECT JSON_EXTRACT(@myselect, "$.query_block.table.key");
    +----------------------------------------------------+
    | JSON_EXTRACT(@myselect, "$.query_block.table.key") |
    +----------------------------------------------------+
    | "PRIMARY"                                          |
    +----------------------------------------------------+
    1 row in set (0.01 sec)
    
    mysql> SELECT JSON_EXTRACT(@myupdate, "$.query_block.table.access_type") AS U_acc,
        ->        JSON_EXTRACT(@mydelete, "$.query_block.table.access_type") AS D_acc;
    +---------+-------+
    | U_acc   | D_acc |
    +---------+-------+
    | "range" | "ALL" |
    +---------+-------+
    1 row in set (0.00 sec)

    请参见第14.17节,“JSON 函数”.

    尝试不包括 FORMAT=JSON 而使用 INTO 子句将导致 EXPLAIN 被拒绝,错误代码为ER_EXPLAIN_INTO_IMPLICIT_FORMAT_NOT_SUPPORTED。无论当前 explain_format 系统变量的值如何。

    不支持在EXPLAIN ANALYZEFOR CONNECTION中使用INTO子句。

    Important

    如果语句被拒绝,用户变量将不会更新。

  • MySQL 8.4 支持 FOR SCHEMA 子句,使EXPLAIN behave 如同语句已经在指定数据库中执行一样;FOR DATABASE 是支持的同义词。下面是一个简单示例:

    mysql> USE b;
    Database changed
    mysql> CREATE SCHEMA s1;
    Query OK, 1 row affected (0.01 sec)
    
    mysql> CREATE SCHEMA s2;
    Query OK, 1 row affected (0.01 sec)
    
    mysql> USE s1;
    Database changed
    mysql> CREATE TABLE t (c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY, c2 INT NOT NULL);
    Query OK, 0 rows affected (0.04 sec)
    
    mysql> USE s2;
    Database changed
    mysql> CREATE TABLE t (c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY, c2 INT NOT NULL, KEY i1 (c2));
    Query OK, 0 rows affected (0.04 sec)
    
    mysql> USE b;
    Database changed
    mysql> EXPLAIN FORMAT=TREE FOR SCHEMA s1 SELECT * FROM t WHERE c2 > 50\G
    *************************** 1. row ***************************
    EXPLAIN: -> Filter: (t.c2 > 50)  (cost=0.35 rows=1)
        -> Table scan on t  (cost=0.35 rows=1)
    
    1 row in set (0.00 sec)
    
    mysql> EXPLAIN FORMAT=TREE FOR SCHEMA s2 SELECT * FROM t WHERE c2 > 50\G
    *************************** 1. row ***************************
    EXPLAIN: -> Filter: (t.c2 > 50)  (cost=0.35 rows=1)
        -> Covering index scan on t using i1  (cost=0.35 rows=1)
    
    1 row in set (0.00 sec)

    如果数据库不存在,语句将被拒绝,错误代码为ER_BAD_DB_ERROR。如果用户没有运行语句所需的权限,它将被拒绝,错误代码为ER_DBACCESS_DENIED_ERROR

    FOR SCHEMA 不兼容 FOR CONNECTION

EXPLAIN 需要执行解释语句所需的相同权限。此外,EXPLAIN 还需要SHOW VIEW 权限,以便解释视图。 EXPLAIN ... FOR CONNECTION 还需要PROCESS 权限,如果指定的连接属于不同用户。

系统变量 explain_format 确定了使用EXPLAIN 显示查询执行计划时的输出格式。这个变量可以取所有与FORMAT 选项相同的值,除了DEFAULTTRADITIONAL 的同义词。以下示例使用了MySQL: Other Downloads 中的 world 数据库中的 country 表:

mysql> USE world; # Make world the current database
Database changed

检查explain_format 的值,我们看到它具有默认值,因此EXPLAIN (没有FORMAT 选项)因此使用传统的表格输出:

mysql> SELECT @@explain_format;
+------------------+
| @@explain_format |
+------------------+
| TRADITIONAL      |
+------------------+
1 row in set (0.00 sec)

mysql> EXPLAIN SELECT Name FROM country WHERE Code Like 'A%';
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | country | NULL       | range | PRIMARY       | PRIMARY | 12      | NULL |   17 |   100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

如果将explain_format的值设置为TREE,然后重新执行相同的EXPLAIN语句,输出结果将使用树形格式:

mysql> SET @@explain_format=TREE;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@explain_format;
+------------------+
| @@explain_format |
+------------------+
| TREE             |
+------------------+
1 row in set (0.00 sec)

mysql> EXPLAIN SELECT Name FROM country WHERE Code LIKE 'A%';
+--------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                      |
+--------------------------------------------------------------------------------------------------------------+
| -> Filter: (country.`Code` like 'A%')  (cost=3.67 rows=17)
    -> Index range scan on country using PRIMARY over ('A' <= Code <= 'A????????')  (cost=3.67 rows=17)  |
+--------------------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)

如前所述,FORMAT选项会覆盖这个设置。使用FORMAT=JSON而不是FORMAT=TREE执行相同的EXPLAIN语句,可以看到这是真的:

mysql> EXPLAIN FORMAT=JSON SELECT Name FROM country WHERE Code LIKE 'A%';
+------------------------------------------------------------------------------+
| EXPLAIN                                                                      |
+------------------------------------------------------------------------------+
| {
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "3.67"
    },
    "table": {
      "table_name": "country",
      "access_type": "range",
      "possible_keys": [
        "PRIMARY"
      ],
      "key": "PRIMARY",
      "used_key_parts": [
        "Code"
      ],
      "key_length": "12",
      "rows_examined_per_scan": 17,
      "rows_produced_per_join": 17,
      "filtered": "100.00",
      "cost_info": {
        "read_cost": "1.97",
        "eval_cost": "1.70",
        "prefix_cost": "3.67",
        "data_read_per_join": "16K"
      },
      "used_columns": [
        "Code",
        "Name"
      ],
      "attached_condition": "(`world`.`country`.`Code` like 'A%')"
    }
  }
}                                                                              |
+------------------------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)

要将EXPLAIN的默认输出结果返回到表格格式,设置explain_formatTRADITIONAL。或者,你也可以设置它为DEFAULT

mysql> SET @@explain_format=DEFAULT;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@explain_format;
+------------------+
| @@explain_format |
+------------------+
| TRADITIONAL      |
+------------------+
1 row in set (0.00 sec)

MySQL 8.4支持两个JSON输出格式的版本。第1个版本是线性格式,始终在MySQL 8.2和更早期版本中使用;在MySQL 8.4中,这仍然是默认值,并且在本节中的示例中已经显示了。第2个版本的JSON输出格式基于访问路径,旨在为未来MySQL优化器版本提供兼容性。你可以通过将explain_json_format_version服务器系统变量设置为2,如同前一个示例中所示的相同EXPLAIN语句:

mysql> SELECT @@explain_json_format_version;
+-------------------------------+
| @@explain_json_format_version |
+-------------------------------+
|                             1 |
+-------------------------------+
1 row in set (0.00 sec)

mysql> SET @@explain_json_format_version = 2;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@explain_json_format_version;
+-------------------------------+
| @@explain_json_format_version |
+-------------------------------+
|                             2 |
+-------------------------------+
1 row in set (0.00 sec)

mysql> EXPLAIN FORMAT=JSON SELECT Name FROM country WHERE Code LIKE 'A%';
+------------------------------------------------------------------------------+
| EXPLAIN                                                                      |
+------------------------------------------------------------------------------+
| {
  "query": "/* select#1 */ select `world`.`country`.`Name` AS `Name` from `world`.`country` where (`world`.`country`.`Code` like 'A%')",
  "inputs": [
    {
      "ranges": [
        "('A' &lt;= Code &lt;= 'A????????')"
      ],
      "covering": false,
      "operation": "Index range scan on country using PRIMARY over ('A' &lt;= Code &lt;= 'A????????')",
      "index_name": "PRIMARY",
      "table_name": "country",
      "access_type": "index",
      "estimated_rows": 17.0,
      "index_access_type": "index_range_scan",
      "estimated_total_cost": 3.668778400708174
    }
  ],
  "condition": "(country.`Code` like 'A%')",
  "operation": "Filter: (country.`Code` like 'A%')",
  "access_type": "filter",
  "estimated_rows": 17.0,
  "estimated_total_cost": 3.668778400708174
}                                                                              |
+------------------------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)

使用Version 2格式后,你可以通过将explain_json_format_version设置回1(默认值),使所有后续的EXPLAIN FORMAT=JSON语句返回到第1个版本的格式。

explain_json_format_version 值确定所有使用该值的 EXPLAIN 语句所用的 JSON 输出格式,是否使用 JSON 格式取决于给定的 EXPLAIN 语句是否包含明确的 FORMAT=JSON 选项,或者是因为系统变量 explain_format 设置为 JSON

使用EXPLAIN,可以查看需要在哪些表中添加索引以提高语句执行速度。您也可以使用EXPLAIN检查优化器是否按最优顺序连接表。要将 optimizer 提示使用与 SELECT 语句中表名的顺序相符的连接顺序,开始语句以 SELECT STRAIGHT_JOIN 而不是SELECT。 (参见第15.2.13节,“SELECT 语句”。)

优化器跟踪可能会提供与EXPLAIN相同的信息,但是优化器跟踪格式和内容在版本之间可能会发生变化。详细信息请见MySQL Internals: Tracing the Optimizer

如果您遇到索引不被使用的问题,认为它们应该被使用,请运行ANALYZE TABLE来更新表统计信息,如键的基数,可以影响优化器的选择。见第15.7.3.1节,“ANALYZE TABLE 语句”

Note

MySQL Workbench 有一个可视化 Explain 能力,提供EXPLAIN输出的可视化表示。见Tutorial: 使用 Explain 提高查询性能

EXPLAIN ANALYZE 执行语句并生成EXPLAIN输出,包括时间和关于优化器期望与实际执行的详细信息。对于每个迭代器,提供以下信息:

  • 预估执行成本

    (某些迭代器未被成本模型所考虑,因此不包括在估算中。)

  • 返回行数的估计

  • 返回第一行的时间

  • 执行该迭代器(包括子迭代器,但不包括父迭代器),以毫秒为单位。

    (当有多个循环时,这个数字显示平均时间每循环。)

  • 迭代器返回的行数

  • 循环次数

查询执行信息使用TREE输出格式,节点表示迭代器。EXPLAIN ANALYZE总是使用TREE输出格式,可以使用FORMAT=TREE来指定;其他格式仍然不支持。

EXPLAIN ANALYZE可以与SELECT语句、多表UPDATEDELETE语句、TABLE语句一起使用。

您可以使用KILL QUERYCTRL-C来终止该语句。

EXPLAIN ANALYZE不能与FOR CONNECTION一起使用。

示例输出:

mysql> EXPLAIN ANALYZE SELECT * FROM t1 JOIN t2 ON (t1.c1 = t2.c2)\G
*************************** 1. row ***************************
EXPLAIN: -> Inner hash join (t2.c2 = t1.c1)  (cost=4.70 rows=6)
(actual time=0.032..0.035 rows=6 loops=1)
    -> Table scan on t2  (cost=0.06 rows=6)
(actual time=0.003..0.005 rows=6 loops=1)
    -> Hash
        -> Table scan on t1  (cost=0.85 rows=6)
(actual time=0.018..0.022 rows=6 loops=1)

mysql> EXPLAIN ANALYZE SELECT * FROM t3 WHERE i > 8\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (t3.i > 8)  (cost=1.75 rows=5)
(actual time=0.019..0.021 rows=6 loops=1)
    -> Table scan on t3  (cost=1.75 rows=15)
(actual time=0.017..0.019 rows=15 loops=1)

mysql> EXPLAIN ANALYZE SELECT * FROM t3 WHERE pk > 17\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (t3.pk > 17)  (cost=1.26 rows=5)
(actual time=0.013..0.016 rows=5 loops=1)
    -> Index range scan on t3 using PRIMARY  (cost=1.26 rows=5)
(actual time=0.012..0.014 rows=5 loops=1)

示例输出中的表格是由以下语句创建的:

CREATE TABLE t1 (
    c1 INTEGER DEFAULT NULL,
    c2 INTEGER DEFAULT NULL
);

CREATE TABLE t2 (
    c1 INTEGER DEFAULT NULL,
    c2 INTEGER DEFAULT NULL
);

CREATE TABLE t3 (
    pk INTEGER NOT NULL PRIMARY KEY,
    i INTEGER DEFAULT NULL
);

该语句的输出中显示的actual time值以毫秒为单位。

explain_formatEXPLAIN ANALYZE 产生以下影响:

  • 如果该变量的值是TRADITIONALTREE(或同义词DEFAULT),则EXPLAIN ANALYZE 使用TREE 格式。这确保了该语句继续使用默认的TREE格式,正如在explain_format引入之前那样。

  • 如果explain_format 的值是 JSON,则EXPLAIN ANALYZE 返回错误,除非指定了FORMAT=TREE 作为语句的一部分。这是因为EXPLAIN ANALYZE 只支持TREE 输出格式。

我们在这里重用上一个示例中的最后一个EXPLAIN ANALYZE 语句,来描述第二点行为:

mysql> SET @@explain_format=JSON;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@explain_format;
+------------------+
| @@explain_format |
+------------------+
| JSON             |
+------------------+
1 row in set (0.00 sec)

mysql> EXPLAIN ANALYZE SELECT * FROM t3 WHERE pk > 17\G
ERROR 1235 (42000): This version of MySQL doesn't yet support 'EXPLAIN ANALYZE with JSON format'

mysql> EXPLAIN ANALYZE FORMAT=TRADITIONAL SELECT * FROM t3 WHERE pk > 17\G
ERROR 1235 (42000): This version of MySQL doesn't yet support 'EXPLAIN ANALYZE with TRADITIONAL format'

mysql> EXPLAIN ANALYZE FORMAT=TREE SELECT * FROM t3 WHERE pk > 17\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (t3.pk > 17)  (cost=1.26 rows=5)
(actual time=0.013..0.016 rows=5 loops=1)
    -> Index range scan on t3 using PRIMARY  (cost=1.26 rows=5)
(actual time=0.012..0.014 rows=5 loops=1)

使用 FORMAT=TRADITIONALFORMAT=JSONEXPLAIN ANALYZE 总是引发错误,不管explain_format 的值是什么。

在 MySQL 8.4 中,EXPLAIN ANALYZEEXPLAIN FORMAT=TREE 输出的数字遵循以下规则:

  • 0.001-999999.5 之间的数字以十进制数形式打印。

    小于1000的十进制数字有三位有效数字,余下的有四、五或六位。

  • 超出范围0.001-999999.5的数字以科学记数法打印。例如,值为1.23e+9934e-6

  • 不打印尾部零。例如,我们打印2.3,而不是2.30,并且打印1.2e+6,而不是1.20e+6

  • 小于1e-12的数字打印为0