MySQL了解MySQL的Explain,读这一篇够了( ̄∇ ̄)/

Posted AQin1012

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL了解MySQL的Explain,读这一篇够了( ̄∇ ̄)/相关的知识,希望对你有一定的参考价值。

目录

ID

select_type 查询类型

table 表名

type 关联类型/访问类型

possible_keys MySQL觉得可能要用到的索引

key 实际用到的索引

key_len 用到的索引的长度(比如可用于判断使用了联合索引中的哪几个)

ref 表查找值所用的列(表名.字段)或常量(const)

row 预估要读取并检测的行数

Extra 额外信息

The EXPLAIN statement provides information about how mysql executes statements.

EXPLAIN提供MySQL如何执行语句的信息,举个例子

EXPLAIN SELECT name FROM app_user WHERE id = 1;

就是在你要执行的查询语句前加一个EXPLAIN,就可以查看执行当前语句的具体信息,比如属于什么查询类型啦~走了什么索引啦(具体如下图)~

总的来说,通过EXPLAIN我们可以获取以下信息:

  • 表的读取顺序

  • 数据读取操作的操作类型

  • 使用了哪些索引

  • 每张表优化器查询了多少行数据

 我们在碰到一些类型“慢查询”问题时,一般会使用EXPLAIN来分析我们的SQL还有哪些地方可以做优化,具体就是根据上图中列表字段进行分析的,本篇就把EXPLAIN的关键字段挖个地儿朝天,👩再也不用担心要如何对SQL进行优化叻( ̄∇ ̄)/

ID

ID越大越先执行,一致按先后顺序执行

select_type 查询类型

这里只列出了常见的几种,想更加深入可以戳官网

https://dev.mysql.com/doc/refman/8.0/en/explain-output.html#explain_select_typehttps://dev.mysql.com/doc/refman/8.0/en/explain-output.html#explain_select_type

  • SIMPLE 简单查询(无联合查询或者子查询)

  • PRIMARY 复杂查询中最外层的select

  • DERIVED from后面的临时表(派生表)

  • SUBQUERY select后面的临时表

  • UNION union中第二个(或者说后一个)select

table 表名

表示结果集出自哪张表

举个🌰,如果某个查询table的值为<devired3>的含义就是这个select是从ID为3的派生表中进行查询的

type 关联类型/访问类型

类型

解释

备注

NULL

在优化阶段分解查询语句,使得在执行阶段无需再访问表/索引

system

无需进行磁盘IO,并且要查询的结果集只有一行记录

属于constant 的特殊情况

constant

查找主键索引,MySQL能对查询的某部分进行优化将其转换为一个常量(走主键(primary key)或者唯一(unique key)索引),所以表中最多有一个匹配行,只需要读取1次

  • 可以使用show warnings;查看优化后的版本

eq_ref

查找唯一性索引,比如表链接时,使用主键/唯一索引进行关联的表查询,最多只会返回1条数据

ref

使用普通索引/非唯一性索引(从根节点开始),或者唯一性索引的部分前缀,索引要进行比较,有可能查出多条结果集

使用了联合主键前缀也是这种类型

range

查询某个索引的部分索引,范围扫描,通常出现在in()/between/>/</>=/<=等操作中,使用一个索引来检索指定范围

查询效率跟结果集大小相关(可使用分页等方式进行优化)

index (全索引扫描)

查找全部索引树,表示扫描全索引可以拿到结果(从1开始遍历),一半是扫描某个二级索引,这种扫描一般不会从索引树根节点开始快速查找,而是直接对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般会使用覆盖索引,二级索引一般比较小(主键索引包含数据,一般会比较大,因而读取的磁盘数据较大),所以index通常会比ALL快一些

  • 如果主键索引和二级索引都包含结果集,MySQL会选用小的索引进行查询

  • 虽然index也使用到了索引,但是和ref使用索引的方式不同,index是通过叶子节点从第一条索引开始向下遍历的,而ref是从索引的根节点开始折半查找,因而ref效率比index快很多

ALL

全表扫描,不使用任何索引,从第一个主键(聚簇索引的所有叶子节点)开始向下查找

possible_keys MySQL觉得可能要用到的索引

key 实际用到的索引

key_len 用到的索引的长度(比如可用于判断使用了联合索引中的哪几个)

类型

具体类型

长度

字符串

char(n)

n字节

varchar(n)

如果是utf-8则长度3n+2 字节,加的2字节用于存储字符串长度

utf-8一个占3字节

数值类型

tinyint

1字节

smallyint

2字节

int

4字节

bigint

8字节

时间类型

date

3字节

timestamp

4字节

NULL

NULL

1字节

允许为空还有1个字段记录是否为空

一般来讲,索引的最大长度是768字节,当字符串过长时,MySQL会做一个类似左前缀索引的处理,将前半部分的字符串提取出来做索引

ref 表查找值所用的列(表名.字段)或常量(const)

这一列显示了在key列记录的索引中,表查找值所用到的列/常量

row 预估要读取并检测的行数

Extra 额外信息

  • Using index

      查询结果集使用了覆盖索引覆盖索引是一种查询方式,表示要查询的结果字段在查询的索引树中全部包括,无需再回表

      覆盖索引是指MySQL执行计划explain结果里的key有使用索引,如果select后面查询的字段都可以从这个索引树中获取,这种情况一般可以说是使用了覆盖索引,extra列中就会显示Using Index;覆盖索引一般针对的是辅助索引,整个查询结果只通过辅助索引就能拿到结果,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其他字段值

    • 优化点

          如果出现回表的情况,即在查询字段中有一个字段没有加索引或者出现索引失效的问题,导致sql回表走了全表扫描,就可以使用覆盖索引进行优化

  • Using index condition

      查询的列不完全被索引覆盖,where条件之前一个前导列的范围

  • Using temporary/Using filesort

      使用了临时表

  • Using where

      使用Where语句处理结果,并且查询到的列未被索引覆盖

……

上面是一些常见的类型,其他还有很多,感兴趣可以参考官网

MySQL :: MySQL 8.0 Reference Manual :: 8.8.2 EXPLAIN Output Format

一文读懂 MySQL Explain 执行计划

一、前言

上周老周的一个好朋友让我出一篇教你读懂 SQL 执行计划,和我另一位读者反馈的面试题如何排查慢 SQL 的强相关,索性先出一篇一文读懂 MySQL Explain 执行计划。Explain 执行计划你一定得会看,不然你简历上就别去写什么你会 SQL 调优,不然面试官会觉得,Explain 执行计划你都不会看,那你还 SQL 调啥优啊?SQL 调你吧???开个小玩笑,玩笑归玩笑,重要是真的重要!!!

二、Explain 执行计划是什么?

什么是执行计划?简而言之,就是 SQL 在数据库中执行时的表现情况,通常用于 SQL 性能分析、优化和加锁分析等场景,执行过程会在 MySQL 查询过程中由解析器,预处理器和查询优化器共同生成。在 MySQL 中使用 explain 关键字来查看。

2.1 执行计划有什么用?

它可以用来分析 SQL 语句和表结构的性能瓶颈

  • 关联查询的执行顺序
  • 查询操作的操作类型
  • 哪些索引可以被命中
  • 哪些索引实际被命中
  • 每张表有多少记录参与查询

2.2 MySQL 执行过程

如上图所示,MySQL 数据库由 Server 层和 Engine 层组成:

  • Server 层有 SQL 分析器、SQL优化器、SQL 执行器,用于负责 SQL 语句的具体执行过程;
  • Engine 层负责存储具体的数据,如最常使用的 MyISAM、InnoDB 存储引擎,还有用于在内存中存储临时结果集的 TempTable 引擎。

SQL 优化器会分析所有可能的执行计划,选择成本最低的执行,这种优化器称之为:CBO(Cost-based Optimizer,基于成本的优化器)。

而在 MySQL 中,一条 SQL 的计算成本计算如下所示:

Cost = Server Cost + Engine Cost
= CPU Cost + IO Cost

其中,CPU Cost 表示计算的开销,比如索引键值的比较、记录值的比较、结果集的排序等这些操作都在 Server 层完成;

IO Cost 表示引擎层 IO 的开销,MySQL 8.0 可以通过区分一张表的数据是否在内存中,分别计算读取内存 IO 开销以及读取磁盘 IO 的开销。

数据库 mysql 下的表 server_cost、engine_cost 则记录了对于各种成本的计算,如:


表 server_cost 记录了 Server 层优化器各种操作的成本,这里面包括了所有 CPU Cost,其具体含义如下:

  • disk_temptable_create_cost:创建磁盘临时表的成本,默认为 20
  • disk_temptable_row_cost:磁盘临时表中每条记录的成本,默认为 0.5
  • key_compare_cost:索引键值比较的成本,默认为 0.05,成本最小。
  • memory_temptable_create_cost:创建内存临时表的成本:默认为 1
  • memory_temptable_row_cost:内存临时表中每条记录的成本,默认为 0.1
  • row_evaluate_cost:记录间的比较成本,默认为 0.1

可以看到, MySQL 优化器认为如果一条 SQL 需要创建基于磁盘的临时表,则这时的成本是最大的,其成本是基于内存临时表的 20 倍。而索引键值的比较、记录之间的比较,其实开销是非常低的,但如果要比较的记录数非常多,则成本会变得非常大。

而表 engine_cost 记录了存储引擎层各种操作的成本,这里包含了所有的 IO Cost,具体含义如下:

  • io_block_read_cost:从磁盘读取一个页的成本,默认值为 1
  • memory_block_read_cost:从内存读取一个页的成本,默认值为 0.25

也就是说, MySQL 优化器认为从磁盘读取的开销是内存开销的 4 倍。

三、Explain 执行计划详解

我们先来准备以下 SQL 脚本:

CREATE TABLE `user` (
	`id` INT (11) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR (20) DEFAULT NULL COMMENT "用户名",
	PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = "用户表";

CREATE TABLE `user_robot_relate` (
	`id` INT (11) NOT NULL AUTO_INCREMENT,
	`user_id` INT (11) NOT NULL COMMENT "用户id",
	`robot_id` INT (11) NOT NULL COMMENT "机器人id",
	PRIMARY KEY (`id`), 
	KEY `idx_user_id` (`user_id`), 
	KEY `idx_robot_id` (`robot_id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = "用户与机器人表";

CREATE TABLE `robot` (
	`id` INT (11) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR ( 20 ) DEFAULT NULL COMMENT "机器人名",
	PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = "机器人表";

INSERT INTO user VALUES (1, 'riemann');
INSERT INTO user VALUES (2, 'andy');

INSERT INTO user_robot_relate VALUES (1, 1, 1);
INSERT INTO user_robot_relate VALUES (2, 1, 2);
INSERT INTO user_robot_relate VALUES (3, 2, 3);

INSERT INTO robot VALUES (1, '小白鲸');
INSERT INTO robot VALUES (2, '扫地机');
INSERT INTO robot VALUES (3, '扫拖一体机');

我们创建三张表 user、user_robot_relate、robot,表之间的关系 user.id = user_robot_relate.user_id AND user_robot_relate.robot_id = robot.id。

先来看下我的 MySQL 版本,是 5.7.37 的。

接着我们看一下执行计划有哪些字段,先看个整体的,让大家有个大概的认识后,我们再逐一去详解分析。


explain 执行后输出的结果集包含 12 列,分别是 id、select_type、table、partitions、type、possible_keys、key、key_len、ref、rows、filtered 和 Extra,下面对这些字段进行解释。

  • id:Query Optimizer 所选定的执行计划中查询的序列号
  • select_type:显示本行是简单或复杂 select。如果查询有任何复杂的子查询,则最外层标记为PRIMARY、DERIVED.、UNION、UNION RESUIT 等。
  • table:显示这一步所访问的数据库中的表的名称
  • partitions:查询时匹配到的分区信息,对于非分区表值为 NULL,当查询的是分区表时,partitions 显示分区表命中的分区情况。
  • type:数据访问、读取操作类型(ALL、index、range、ref、eq_ref、const、system)等
  • possible_keys:该查询可以利用的索引,如果没有任何索引可以使用,就会显示成 null,这一
    项内容对于优化时候索引的调整非常重要。
  • key:MySQL Query Optimizer 从 possible_keys 中所选择使用的索引
  • key_len:被选中使用索引的索引键长度
  • ref:列出是通过常量(const),还是某个表的某个字段(如果是 join)来过滤(通过 key)
    的。
  • rows:MySQL Query Optimizer 通过系统收集到的统计信息估算出来的结果集记录条数
  • filtered:表示存储引擎返回的数据在经过过滤后,剩下满足条件的记录数量的比例。
  • Extra:查询中每一步实现的额外细节信息,如 Using filesort、index 等。

3.1 id

看到三条记录的 id 都相同,可以理解成这三个表为一组,具有同样的优先级,执行顺序由上而下,具体顺序由优化器决定。

3.1.1 id 相同

mysql> EXPLAIN SELECT * FROM user u WHERE u.id = (SELECT ur.user_id FROM user_robot_relate ur WHERE ur.robot_id = (SELECT r.id FROM robot r WHERE r.name = '扫地机'));
+----+-------------+-------+------------+------+--------------------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys            | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+--------------------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | ur    | NULL       | ALL  | idx_user_id,idx_robot_id | NULL | NULL    | NULL |    3 |   100.00 | NULL                                               |
|  1 | SIMPLE      | u     | NULL       | ALL  | PRIMARY                  | NULL | NULL    | NULL |    2 |    50.00 | Using where; Using join buffer (Block Nested Loop) |
|  1 | SIMPLE      | r     | NULL       | ALL  | PRIMARY                  | NULL | NULL    | NULL |    3 |    50.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+--------------------------+------+---------+------+------+----------+----------------------------------------------------+

3.1.2 id 不同

如果我们的 SQL 中存在子查询,那么 id 的序号会递增,id 值越大优先级越高,越先被执行。当三个表依次嵌套,发现最里层的子查询 id 最大,最先执行。

mysql> EXPLAIN SELECT * FROM user u WHERE u.id = (SELECT ur.user_id FROM user_robot_relate ur WHERE ur.robot_id = (SELECT r.id FROM robot r WHERE r.name = '扫地机'));
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key          | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+
|  1 | PRIMARY     | u     | NULL       | const | PRIMARY       | PRIMARY      | 4       | const |    1 |   100.00 | NULL        |
|  2 | SUBQUERY    | ur    | NULL       | ref   | idx_robot_id  | idx_robot_id | 4       | const |    1 |   100.00 | Using where |
|  3 | SUBQUERY    | r     | NULL       | ALL   | NULL          | NULL         | NULL    | NULL  |    3 |    33.33 | Using where |
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+

3.1.3 以上两种同时存在

将上边的 SQL 稍微修改一下,增加一个子查询,发现 id 的以上两种同时存在。相同 id 划分为一组,这样就有三个组,同组的从上往下顺序执行,不同组 id 值越大,优先级越高,越先执行。

mysql> EXPLAIN SELECT * FROM user u WHERE u.id = (SELECT ur.user_id FROM user_robot_relate ur WHERE ur.robot_id = (SELECT r.id FROM robot r WHERE r.name = '扫地机')) AND u.id IN (SELECT u.id FROM user u WHERE u.name = 'riemann');
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key          | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+
|  1 | PRIMARY     | u     | NULL       | const | PRIMARY       | PRIMARY      | 4       | const |    1 |   100.00 | NULL        |
|  1 | PRIMARY     | u     | NULL       | const | PRIMARY       | PRIMARY      | 4       | const |    1 |   100.00 | NULL        |
|  2 | SUBQUERY    | ur    | NULL       | ref   | idx_robot_id  | idx_robot_id | 4       | const |    1 |   100.00 | Using where |
|  3 | SUBQUERY    | r     | NULL       | ALL   | NULL          | NULL         | NULL    | NULL  |    3 |    33.33 | Using where |
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+

3.2 select_type

select_type:表示 select 查询的类型,主要是用于区分各种复杂的查询,例如:普通查询、联合查询、子查询等。

  • SIMPLE:表示最简单的 select 查询语句,也就是在查询中不包含子查询或者 union 交并差集等操作。

  • PRIMARY:当查询语句中包含任何复杂的子部分,最外层查询则被标记为 PRIMARY。

  • SUBQUERY:当 select 或 where 列表中包含了子查询,该子查询被标记为 SUBQUERY。

  • DERIVED:表示包含在 from 子句中的子查询的 select,在我们的 from 列表中包含的子查询会被标记为 derived。

  • UNION:如果 union 后边又出现的 select 语句,则会被标记为 union;若 union 包含在 from 子句的子查询中,外层 select 将被标记为 derived。

  • UNION RESULT:代表从 union 的临时表中读取数据,而 table 列的 <union1,4> 表示用第一个和第四个 select 的结果进行 union 操作。

    mysql> EXPLAIN SELECT t.user_id, (SELECT u.id FROM user u) o FROM (SELECT ur.user_id, ur.robot_id FROM user_robot_relate ur WHERE ur.id = 2) t UNION (SELECT r.id, r.name FROM robot r);
    +----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
    | id | select_type  | table      | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra           |
    +----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
    |  1 | PRIMARY      | ur         | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL            |
    |  2 | SUBQUERY     | u          | NULL       | index | NULL          | PRIMARY | 4       | NULL  |    2 |   100.00 | Using index     |
    |  4 | UNION        | r          | NULL       | ALL   | NULL          | NULL    | NULL    | NULL  |    3 |   100.00 | NULL            |
    | NULL | UNION RESULT | <union1,4> | NULL       | ALL   | NULL          | NULL    | NULL    | NULL  | NULL |     NULL | Using temporary |
    +----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
    

3.3 table

查询的表名,并不一定是真实存在的表,有别名显示别名,也可能为临时表,例如上边的 DERIVED、 <union1,4> 等。

3.4 partitions

查询时匹配到的分区信息,对于非分区表值为 NULL,当查询的是分区表时,partitions 显示分区表命中的分区情况。

3.5 type

type:查询使用了何种类型,它在 SQL优化中是一个非常重要的指标,以下性能从好到坏依次是:system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

  • system:当表仅有一行记录时(系统表),数据量很少,往往不需要进行磁盘 IO,速度非常快。

  • const:表示查询时命中 primary key 主键或者 unique 唯一索引,或者被连接的部分是一个常量(const)值。这类扫描效率极高,返回数据量少,速度非常快。

    mysql> EXPLAIN SELECT * FROM robot WHERE id = 1;
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | robot | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
    
  • eq_ref:查询时命中主键 primary key 或者 unique key 索引, type 就是 eq_ref。

    mysql> EXPLAIN SELECT u.name FROM user u, user_robot_relate ur WHERE u.id = ur.id; 
    +----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------------+
    | id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref       | rows | filtered | Extra       |
    +----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------------+
    |  1 | SIMPLE      | u     | NULL       | ALL    | PRIMARY       | NULL    | NULL    | NULL      |    2 |   100.00 | NULL        |
    |  1 | SIMPLE      | ur    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | test.u.id |    1 |   100.00 | Using index |
    +----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------------+
    
  • ref:区别于 eq_ref,ref 表示使用非唯一性索引,会找到很多个符合条件的行。

    mysql> EXPLAIN SELECT id FROM user_robot_relate WHERE user_id = 2; 
    +----+-------------+-------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------------+
    | id | select_type | table             | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra       |
    +----+-------------+-------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------------+
    |  1 | SIMPLE      | user_robot_relate | NULL       | ref  | idx_user_id   | idx_user_id | 4       | const |    1 |   100.00 | Using index |
    +----+-------------+-------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------------+
    
  • ref_or_null:这种连接类型类似于 ref,区别在于 MySQL 会额外搜索包含 NULL 值的行。

    -- 为了模拟这个场景,我又新增了一个 user_test 表。
    mysql> CREATE TABLE `user_test` (
        -> `id` INT (11) NOT NULL AUTO_INCREMENT,
        -> `name` VARCHAR (20) DEFAULT NULL COMMENT "用户名",
        -> PRIMARY KEY (`id`), 
        -> KEY `idx_name` (`name`)
        -> ) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = "用户测试表";
    Query OK, 0 rows affected (0.01 sec)
    
    mysql> EXPLAIN SELECT id FROM user_test WHERE name = 'riemann' OR name IS NULL; 
    +----+-------------+-----------+------------+-------------+---------------+----------+---------+-------+------+----------+--------------------------+
    | id | select_type | table     | partitions | type        | possible_keys | key      | key_len | ref   | rows | filtered | Extra                    |
    +----+-------------+-----------+------------+-------------+---------------+----------+---------+-------+------+----------+--------------------------+
    |  1 | SIMPLE      | user_test | NULL       | ref_or_null | idx_name      | idx_name | 83      | const |    2 |   100.00 | Using where; Using index |
    +----+-------------+-----------+------------+-------------+---------------+----------+---------+-------+------+----------+--------------------------+
    
  • index_merge:使用了索引合并优化方法,查询使用了两个以上的索引。

    -- 下边示例中同时使用到主键 id 和字段 user_id 的索引。
    mysql> EXPLAIN SELECT * FROM user_robot_relate WHERE id > 1 AND user_id = 2; 
    +----+-------------+-------------------+------------+-------------+---------------------+---------------------+---------+------+------+----------+---------------------------------------------------+
    | id | select_type | table             | partitions | type        以上是关于MySQL了解MySQL的Explain,读这一篇够了( ̄∇ ̄)/的主要内容,如果未能解决你的问题,请参考以下文章

    了解 EXPLAIN 语句的输出 (MySQL)

    在MySQL中使用explain查询SQL的执行计划

    在MySQL中使用explain查询SQL的执行计划

    在MySQL中使用explain查询SQL的执行计划

    在MySQL中使用explain查询SQL的执行计划

    nginx读这一篇就够了