9-2 MySQL 分析查询语句:EXPLAIN(详细说明)

9-2 MySQL 分析查询语句:EXPLAIN(详细说明)

文章目录

  • [9-2 MySQL 分析查询语句:EXPLAIN(详细说明)](#9-2 MySQL 分析查询语句:EXPLAIN(详细说明))
  • [1. EXPLAIN 概述](#1. EXPLAIN 概述)
  • [2. EXPLAIN 基本语法](#2. EXPLAIN 基本语法)
  • [3. 数据准备](#3. 数据准备)
  • [4. EXPLAIN各列作用](#4. EXPLAIN各列作用)
    • [1. table](#1. table)
    • [2. id](#2. id)
    • [3. select_type](#3. select_type)
    • [4. partitions (可略)](#4. partitions (可略))
    • [5. type(重点)](#5. type(重点))
    • [6. possible_keys和key](#6. possible_keys和key)
    • [7. key_len(重点)](#7. key_len(重点))
    • [8. ref](#8. ref)
    • [9. rows(重点)](#9. rows(重点))
    • [10. filtered](#10. filtered)
    • [11. Extra(重点)](#11. Extra(重点))
    • [12. 小结:](#12. 小结:)
  • [5. EXPLAIN的进一步使用](#5. EXPLAIN的进一步使用)
    • [5.1 EXPLAIN 四种输出格式](#5.1 EXPLAIN 四种输出格式)
    • [5.2 SHOW WARNINGS的使用](#5.2 SHOW WARNINGS的使用)
  • [6. 分析优化器执行计划:trace](#6. 分析优化器执行计划:trace)
  • [7. MySQL监控分析视图-sys schema](#7. MySQL监控分析视图-sys schema)
    • [7.1 Sys schema 视图摘要](#7.1 Sys schema 视图摘要)
    • [7.2 Sys schema视图使用场景](#7.2 Sys schema视图使用场景)
  • [8. 小结:](#8. 小结:)
  • [附件:测试 + 对应学习 SQL 脚本内容](#附件:测试 + 对应学习 SQL 脚本内容)
  • 最后:

这篇文章是我蹲在《尚硅谷》-康师傅博主家的 WiFi 上(不是),连夜 Ctrl+C / V 俩的镇站神文。

这篇转载只是为了,跟大家分享好内容,没有任何商业用途。如果你喜欢这篇文章,请一定要去原作者 B站《尚硅谷-MySQL从菜鸟到大牛》看看,说不定还能发现更多宝藏内容呢!

1. EXPLAIN 概述

官网介绍:MySQL :: MySQL 8.0 Reference Manual :: 10.8.2 EXPLAIN Output Format

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

版本情况:

  • MySQL 5.6.3以前只能 EXPLAIN SELECT ;MYSQL 5.6.3以后就可以 EXPLAIN SELECT,UPDATE, DELETE
  • 在5.7以前的版本中,想要显示 partitions 需要使用 explain partitions 命令;想要显示 filtered 需要使用 explain extended 命令。在5.7版本后,默认explain直接显示partitions和
    filtered中的信息。

2. EXPLAIN 基本语法

EXPLAIN 或 DESCRIBE语句的语法形式如下:

mysql 复制代码
EXPLAIN SELECT select_options
或者
DESCRIBE SELECT select_options

如果我们想看看某个查询的执行计划的话,可以在具体的查询语句前边加一个 EXPLAIN ,就像这样:

mysql 复制代码
mysql> EXPLAIN SELECT 1;

EXPLAIN 语句输出的各个列的作用如下:

在这里把它们都列出来知识为了描述一个轮廓,让大家有一个大致的印象。

3. 数据准备

1. 建表

mysql 复制代码
CREATE TABLE s1 (
id INT AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
INDEX idx_key1 (key1),
UNIQUE INDEX idx_key2 (key2),
INDEX idx_key3 (key3),
INDEX idx_key_part(key_part1, key_part2, key_part3)
) ENGINE=INNODB CHARSET=utf8;
mysql 复制代码
CREATE TABLE s2 (
id INT AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
INDEX idx_key1 (key1),
UNIQUE INDEX idx_key2 (key2),
INDEX idx_key3 (key3),
INDEX idx_key_part(key_part1, key_part2, key_part3)
) ENGINE=INNODB CHARSET=utf8;

2. 设置参数 log_bin_trust_function_creators

创建函数,假如报错,需开启如下命令:允许创建函数设置:

mysql 复制代码
set global log_bin_trust_function_creators=1;    # 不加global只是当前窗口有效。

3. 创建函数

mysql 复制代码
DELIMITER //
CREATE FUNCTION rand_string1(n INT)
    RETURNS VARCHAR(255) #该函数会返回一个字符串
BEGIN
    DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
    DECLARE return_str VARCHAR(255) DEFAULT '';
    DECLARE i INT DEFAULT 0;
    WHILE i < n DO
         SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1)); SET i = i + 1;
    END WHILE;
    RETURN return_str;
    END //
DELIMITER ;

4. 创建存储过程

创建往s1表中插入数据的存储过程:

mysql 复制代码
DELIMITER //
CREATE PROCEDURE insert_s1 (IN min_num INT (10),IN max_num INT (10))
BEGIN
    DECLARE i INT DEFAULT 0;
    SET autocommit = 0;
    REPEAT
    SET i = i + 1;
    INSERT INTO s1 VALUES(
    (min_num + i),
    rand_string1(6),
    (min_num + 30 * i + 5),
    rand_string1(6),
    rand_string1(10),
    rand_string1(5),
    rand_string1(10),
    rand_string1(10)); 
    UNTIL i = max_num
    END REPEAT;
    COMMIT;
END //
DELIMITER ;

创建往s2表中插入数据的存储过程:

mysql 复制代码
DELIMITER //
CREATE PROCEDURE insert_s2 (IN min_num INT (10),IN max_num INT (10))
BEGIN
   DECLARE i INT DEFAULT 0;
   SET autocommit = 0;
   REPEAT
   SET i = i + 1;
   INSERT INTO s2 VALUES(
     (min_num + i),
     rand_string1(6),
     (min_num + 30 * i + 5),
     rand_string1(6),
     rand_string1(10),
     rand_string1(5),
     rand_string1(10),
     rand_string1(10));
  UNTIL i = max_num
  END REPEAT;
  COMMIT;
END //
DELIMITER ;

5. 调用存储过程

s1表数据的添加:加入1万条记录:

mysql 复制代码
CALL insert_s1(10001,10000);

s2表数据的添加:加入1万条记录:

mysql 复制代码
CALL insert_s2(10001,10000);

6. 检查是否将数据成功插入到数据表当中了

mysql 复制代码
SELECT COUNT(*) FROM s1;

SELECT COUNT(*) FROM s2;

4. EXPLAIN各列作用

为了让大家有比较好的体验,我们调整了下 EXPLAIN 输出列的顺序。

1. table

不论我们的查询语句有多复杂,里边儿 包含了多少个表 ,到最后也是需要对每个表进行 单表访问 的,所 以MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该 表的表名(有时不是真实的表名字,可能是简称)。

mysql 复制代码
mysql > EXPLAIN SELECT * FROM s1;

这个查询语句只涉及对s1表的单表查询,所以 EXPLAIN 输出中只有一条记录,其中的table列的值为s1,表明这条记录是用来说明对s1表的单表访问方法的。

下边我们看一个连接查询的执行计划

mysql 复制代码
mysql > EXPLAIN SELECT * FROM s1 INNER JOIN s2;

可以看出这个连接查询的执行计划中有两条记录,这两条记录的table列分别是s1和s2,这两条记录用来分别说明对s1表和s2表的访问方法是什么。

2. id

我们写的查询语句一般都以 SELECT 关键字开头,比较简单的查询语句里只有一个 SELECT 关键字,比 如下边这个查询语句:

mysql 复制代码
SELECT * FROM s1 WHERE key1 = 'a';

稍微复杂一点的连接查询中也只有一个 SELECT 关键字,比如:

mysql 复制代码
SELECT * FROM s1 INNER JOIN s2
ON s1.key1 = s2.key1
WHERE s1.common_field = 'a';

但是下边两种情况下在一条查询语句中会出现多个SELECT关键字:

mysql 复制代码
mysql > EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';

对于连接查询来说,一个SELECT关键字后边的FROM字句中可以跟随多个表,所以在连接查询的执行计划中,每个表都会对应一条记录,但是这些记录的id值都是相同的,比如:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2;

可以看到,上述连接查询中参与连接的s1和s2表分别对应一条记录,但是这两条记录对应的id都是1。这里需要大家记住的是,在连接查询的执行计划中,每个表都会对应一条记录,这些记录的id列的值是相同的 ,出现在前边的表表示驱动表,出现在后面的表表示被驱动表。所以从上边的EXPLAIN输出中我们可以看到,查询优化器准备让s1表作为驱动表,让s2表作为被驱动表来执行查询。

对于包含子查询的查询语句来说,就可能涉及多个SELECT关键字,所以在**包含子查询的查询语句的执行计划中,每个SELECT关键字都会对应一个唯一的id值,比如这样:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
mysql 复制代码
# 查询优化器可能对涉及子查询的查询语句进行重写,转变为多表查询的操作。  
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field = 'a');

可以看到,虽然我们的查询语句是一个子查询,但是执行计划中s1和s2表对应的记录的id值全部是1,这就表明查询优化器将子查询转换为了连接查询

对于包含UNION子句的查询语句来说,每个SELECT关键字对应一个id值也是没错的,不过还是有点儿特别的东西,比方说下边的查询:

mysql 复制代码
# Union去重
mysql> EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 UNION ALL SELECT * FROM s2;

小结:

  • id如果相同,可以认为是一组,从上往下顺序执行
  • 在所有组中,id值越大,优先级越高,越先执行
  • 关注点:id号每个号码,表示一趟独立的查询, 一个sql的查询趟数越少越好

3. select_type

具体分析如下:

  • SIMPLE

    查询语句中不包含UNION或者子查询的查询都算作是SIMPLE类型,比方说下边这个单表查询select_type的值就是SIMPLE:

    mysql 复制代码
    mysql> EXPLAIN SELECT * FROM s1;

当然,连接查询也算是 SIMPLE 类型,比如:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2;
  • PRIMARY

对于包含UNION、UNION ALL或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的select_type的值就是PRIMARY,比方说:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
  • 从结果中可以看到,最左边的小查询SELECT * FROM s1对应的是执行计划中的第一条记录,它的select_type的值就是PRIMARY

  • UNION

    对于包含UNION或者UNION ALL的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询意外,其余的小查询的select_type值就是UNION,可以对比上一个例子的效果。

  • UNION RESULT

    MySQL 选择使用临时表来完成UNION查询的去重工作,针对该临时表的查询的select_type就是UNION RESULT, 例子上边有。

  • SUBQUERY

    如果包含子查询的查询语句不能够转为对应的semi-join的形式,并且该子查询是不相关子查询,并且查询优化器决定采用将该子查询物化的方案来执行该子查询时,该子查询的第一个SELECT关键字代表的那个查询的select_type就是SUBQUERY,比如下边这个查询:

    mysql 复制代码
    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
  • DEPENDENT SUBQUERY
mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 = s2.key2) OR key3 = 'a';
  • DEPENDENT UNION
mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');
  • DERIVED
mysql 复制代码
mysql> EXPLAIN SELECT * FROM (SELECT key1, count(*) as c FROM s1 GROUP BY key1) AS derived_s1 where c > 1;
  • 从执行计划中可以看出,id为2的记录就代表子查询的执行方式,它的select_type是DERIVED, 说明该子查询是以物化的方式执行的。id为1的记录代表外层查询,大家注意看它的table列显示的是derived2,表示该查询时针对将派生表物化之后的表进行查询的。

  • MATERIALIZED

    当查询优化器在执行包含子查询的语句时,选择将子查询物化之后的外层查询进行连接查询时,该子查询对应的select_type属性就是DERIVED,比如下边这个查询:

    mysql 复制代码
    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2);
  • UNCACHEABLE SUBQUERY

    不常用,就不多说了。

  • UNCACHEABLE UNION

    不常用,就不多说了。

4. partitions (可略)

mysql 复制代码
-- 创建分区表,
-- 按照id分区,id<100 p0分区,其他p1分区
CREATE TABLE user_partitions (id INT auto_increment,
NAME VARCHAR(12),PRIMARY KEY(id))
PARTITION BY RANGE(id)(
PARTITION p0 VALUES less than(100),
PARTITION p1 VALUES less than MAXVALUE
);
mysql 复制代码
DESC SELECT * FROM user_partitions WHERE id>200;

查询id大于200(200>100,p1分区)的记录,查看执行计划,partitions是p1,符合我们的分区规则

5. type(重点)

执行计划的一条记录就代表着MySQL对某个表的 执行查询时的访问方法 , 又称"访问类型",其中的 type 列就表明了这个访问方法是啥,是较为重要的一个指标。比如,看到type列的值是ref,表明MySQL即将使用ref访问方法来执行对s1表的查询。

完整的访问方法如下: system , const , eq_ref , ref , fulltext , ref_or_null , index_merge , unique_subquery , index_subquery , range , index , ALL

我们详细解释一下:

  • system

    当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是system。比方说我们新建一个MyISAM表,并为其插入一条记录:

mysql 复制代码
mysql> CREATE TABLE t(i int) Engine=MyISAM;
Query OK, 0 rows affected (0.05 sec)

mysql> INSERT INTO t VALUES(1);
Query OK, 1 row affected (0.01 sec)

然后我们看一下查询这个表的执行计划:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM t;
  • 可以看到type列的值就是system了,

    测试,可以把表改成使用InnoDB存储引擎,试试看执行计划的type列是什么。ALL

  • const

    当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const, 比如:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE id = 10005;
  • eq_ref

在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较)。则对该被驱动表的访问方法就是eq_ref,比方说:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;

从执行计划的结果中可以看出,MySQL打算将s2作为驱动表,s1作为被驱动表,重点关注s1的访问 方法是 eq_ref ,表明在访问s1表的时候可以 通过主键的等值匹配 来进行访问。

  • ref

    当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref,比方说下边这个查询:

    mysql 复制代码
    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
  • fulltext

    全文索引

  • ref_or_null

    当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是ref_or_null,比如说:

    mysql 复制代码
    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL;
  • index_merge

一般情况下对于某个表的查询只能使用到一个索引,但单表访问方法时在某些场景下可以使用Interseation、union、Sort-Union这三种索引合并的方式来执行查询。我们看一下执行计划中是怎么体现MySQL使用索引合并的方式来对某个表执行查询的:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';

从执行计划的 type 列的值是 index_merge 就可以看出,MySQL 打算使用索引合并的方式来执行 对 s1 表的查询。

  • unique_subquery

    类似于两表连接中被驱动表的eq_ref访问方法,unique_subquery是针对在一些包含IN子查询的查询语句中,如果查询优化器决定将IN子查询转换为EXISTS子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的type列的值就是unique_subquery,比如下边的这个查询语句:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 where s1.key1 = s2.key1) OR key3 = 'a';
  • index_subquery

    index_subqueryunique_subquery 类似,只不过访问子查询中的表时使用的是普通的索引,比如这样:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE common_field IN (SELECT key3 FROM s2 where s1.key1 = s2.key1) OR key3 = 'a';
  • range
mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');

或者

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'a' AND key1 < 'b';
  • index

当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index,比如这样:

mysql 复制代码
mysql> EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a';

上述查询中的所有列表中只有key_part2 一个列,而且搜索条件中也只有 key_part3 一个列,这两个列又恰好包含在idx_key_part这个索引中,可是搜索条件key_part3不能直接使用该索引进行refrange方式的访问,只能扫描整个idx_key_part索引的记录,所以查询计划的type列的值就是index

再一次强调,对于使用InnoDB存储引擎的表来说,二级索引的记录只包含索引列和主键列的值,而聚簇索引中包含用户定义的全部列以及一些隐藏列,所以扫描二级索引的代价比直接全表扫描,也就是扫描聚簇索引的代价更低一些。

  • ALL

最熟悉的全表扫描,就不多说了,直接看例子:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1;

小结:

结果值从最好到最坏依次是:

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

其中比较重要的几个提取出来(见上图中的粗体)。SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。(阿里巴巴 开发手册要求)

6. possible_keys和key

在EXPLAIN语句输出的执行计划中,possible_keys列表示在某个查询语句中,对某个列执行单表查询时可能用到的索引有哪些。一般查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。key列表示实际用到的索引有哪些,如果为NULL,则没有使用索引。比方说下面这个查询:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';

上述执行计划的possible_keys列的值是idx_key1, idx_key3,表示该查询可能使用到idx_key1, idx_key3两个索引,然后key列的值是idx_key3,表示经过查询优化器计算使用不同索引的成本后,最后决定采用idx_key3

7. key_len(重点)

实际使用到的索引长度 (即:字节数)

帮你检查是否充分的利用了索引值越大越好,主要针对于联合索引,有一定的参考意义。

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE id = 10005;

int 占用 4 个字节

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;

key2上有一个唯一性约束,是否为NULL占用一个字节,那么就是5个字节

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';

key1 VARCHAR(100) 一个字符占3个字节,100*3,是否为NULL占用一个字节,varchar的长度信息占两个字节。

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a';
mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';

联合索引中可以比较,key_len=606的好于key_len=303

练习:

key_len的长度计算公式:

mysql 复制代码
varchar(10)变长字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)

varchar(10)变长字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+2(变长字段)

char(10)固定字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)

char(10)固定字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)

8. ref

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';

可以看到ref列的值是const,表明在使用idx_key1索引执行查询时,与key1列作等值匹配的对象是一个常数,当然有时候更复杂一点:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);

9. rows(重点)

预估的需要读取的记录条数,值越小越好

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';

10. filtered

某个表经过搜索条件过滤后剩余记录条数的百分比

如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条。

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND common_field = 'a';

对于单表查询来说,这个filtered的值没有什么意义,我们更关注在连接查询中驱动表对应的执行计划记录的filtered值,它决定了被驱动表要执行的次数 (即: rows * filtered)

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';

从执行计划中可以看出来,查询优化器打算把s1作为驱动表,s2当做被驱动表。我们可以看到驱动表s1表的执行计划的rows列为9688,filtered列为10.00,这意味着驱动表s1的扇出值就是9688 x 10.00% = 968.8,这说明还要对被驱动表执行大约968次查询。

11. Extra(重点)

顾名思义,Extra列是用来说明一些额外信息的,包含不适合在其他列中显示但十分重要的额外信息。我们可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句。MySQL提供的额外信息有好几十个,我们就不一个一个介绍了,所以我们只挑选比较重要的额外信息介绍给大家。

  • No tables used

    当查询语句没有FROM子句时将会提示该额外信息,比如:

    mysql 复制代码
    mysql> EXPLAIN SELECT 1;
  • Impossible WHERE

当查询语句的WHERE子句永远为FALSE时将会提示该额外信息

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE 1 != 1;
  • Using where
mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE common_field = 'a';
mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND common_field = 'a';
  • No matching min/max row

当查询列表处有MIN或者MAX聚合函数,但是并没有符合WHERE子句中的搜索条件的记录时。

mysql 复制代码
mysql> EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg';
  • Using index

当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用覆盖索引的情况下,在Extra列将会提示该额外信息。比方说下边这个查询中只需要用到idx_key1而不需要回表操作:

mysql 复制代码
mysql> EXPLAIN SELECT key1 FROM s1 WHERE key1 = 'a';
  • Using index condition

有些搜索条件中虽然出现了索引列,但却不能使用到索引,比如下边这个查询:

mysql 复制代码
SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a';
mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%b';
  • Using join buffer (Block Nested Loop)

在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫join buffer的内存块来加快查询速度,也就是我们所讲的基于块的嵌套循环算法

复制代码
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field;
  • Not exists

当我们使用左(外)连接时,如果WHERE子句中包含要求被驱动表的某个列等于NULL值的搜索条件,而且那个列是不允许存储NULL值的,那么在该表的执行计划的Extra列就会提示这个信息:

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;
  • Using intersect(...) 、 Using union(...) 和 Using sort_union(...)

    如果执行计划的Extra列出现了Using intersect(...)提示,说明准备使用Intersect索引合并的方式执行查询,括号中的...表示需要进行索引合并的索引名称;

    如果出现Using union(...)提示,说明准备使用Union索引合并的方式执行查询;

    如果出现Using sort_union(...)提示,说明准备使用Sort-Union索引合并的方式执行查询。

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
  • Zero limit

当我们的LIMIT子句的参数为0时,表示压根儿不打算从表中读取任何记录,将会提示该额外信息

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 LIMIT 0;
  • Using filesort

有一些情况下对结果集中的记录进行排序是可以使用到索引的。

mysql 复制代码
mysql> EXPLAIN SELECT * FROM s1 ORDER BY key1 LIMIT 10;

需要注意的是,如果查询中需要使用filesort的方式进行排序的记录非常多,那么这个过程是很耗费性能的,我们最好想办法将使用文件排序的执行方式改为索引进行排序

  • Using temporary
mysql 复制代码
mysql> EXPLAIN SELECT DISTINCT common_field FROM s1;

再比如:

mysql 复制代码
mysql> EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field;

执行计划中出现Using temporary并不是一个好的征兆,因为建立与维护临时表要付出很大的成本的,所以我们最好能使用索引来替代掉使用临时表,比方说下边这个包含GROUP BY子句的查询就不需要使用临时表:

mysql 复制代码
mysql> EXPLAIN SELECT key1, COUNT(*) AS amount FROM s1 GROUP BY key1;

ExtraUsing index 的提示里我们可以看出,上述查询只需要扫描 idx_key1 索引就可以搞 定了,不再需要临时表了。

  • 其他,其它特殊情况这里省略

12. 小结:

  • EXPLAIN不考虑各种Cache
  • EXPLAIN不能显示MySQL在执行查询时所作的优化工作
  • EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
  • 部分统计信息是估算的,并非精确值

5. EXPLAIN的进一步使用

5.1 EXPLAIN 四种输出格式

这里谈谈EXPLAIN的输出格式。EXPLAIN可以输出四种格式: 传统格式JSON格式TREE格式 以及 可视化输出 。用户可以根据需要选择适用于自己的格式。

1. 传统格式:

传统格式简单明了,输出是一个表格形式,概要说明查询计划。

mysql 复制代码
mysql> EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;
  1. JSON格式

第1种格式中介绍的EXPLAIN语句输出中缺少了一个衡量执行好坏的重要属性 ------ 成本。而JSON格式是四种格式里面输出信息最详尽的格式,里面包含了执行的成本信息。

  • JSON格式:在EXPLAIN单词和真正的查询语句中间加上 FORMAT=JSON 。
mysql 复制代码
EXPLAIN FORMAT=JSON SELECT ....
  • EXPLAIN的Column与JSON的对应关系:(来源于MySQL 5.7文档)

这样我们就可以得到一个json格式的执行计划,里面包含该计划花费的成本。比如这样:

mysql 复制代码
mysql> EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 WHERE s1.common_field = 'a'\G

我们使用 # 后边跟随注释的形式为大家解释了 EXPLAIN FORMAT=JSON 语句的输出内容,但是大家可能 有疑问 "cost_info" 里边的成本看着怪怪的,它们是怎么计算出来的?先看 s1 表的 "cost_info" 部 分:

json 复制代码
"cost_info": {
    "read_cost": "1840.84",
    "eval_cost": "193.76",
    "prefix_cost": "2034.60",
    "data_read_per_join": "1M"
}
  • read_cost 是由下边这两部分组成的:

    • IO 成本
    • 检测 rows × (1 - filter) 条记录的 CPU 成本

    小贴士: rows和filter都是我们前边介绍执行计划的输出列,在JSON格式的执行计划中,rows 相当于rows_examined_per_scan,filtered名称不变。

  • eval_cost 是这样计算的:

    检测 rows × filter 条记录的成本。

  • prefix_cost 就是单独查询 s1 表的成本,也就是:

    read_cost + eval_cost

  • data_read_per_join 表示在此次查询中需要读取的数据量。

对于 s2 表的 "cost_info" 部分是这样的:

json 复制代码
"cost_info": {
    "read_cost": "968.80",
    "eval_cost": "193.76",
    "prefix_cost": "3197.16",
    "data_read_per_join": "1M"
}

由于 s2 表是被驱动表,所以可能被读取多次,这里的read_costeval_cost 是访问多次 s2 表后累加起来的值,大家主要关注里边儿的 prefix_cost 的值代表的是整个连接查询预计的成本,也就是单次查询 s1 表和多次查询 s2 表后的成本的和,也就是:

mysql 复制代码
968.80 + 193.76 + 2034.60 = 3197.16

3. TREE格式

TREE格式是8.0.16版本之后引入的新格式,主要根据查询的 各个部分之间的关系各部分的执行顺序 来描述如何查询。

sh 复制代码
mysql> EXPLAIN FORMAT=tree SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 WHERE
s1.common_field = 'a'\G
*************************** 1. row ***************************
EXPLAIN: -> Nested loop inner join (cost=1360.08 rows=990)
-> Filter: ((s1.common_field = 'a') and (s1.key1 is not null)) (cost=1013.75
rows=990)
-> Table scan on s1 (cost=1013.75 rows=9895)
-> Single-row index lookup on s2 using idx_key2 (key2=s1.key1), with index
condition: (cast(s1.key1 as double) = cast(s2.key2 as double)) (cost=0.25 rows=1)
1 row in set, 1 warning (0.00 sec)

4. 可视化输出

可视化输出,可以通过MySQL Workbench可视化查看MySQL的执行计划。通过点击Workbench的放大镜图标,即可生成可视化的查询计划。

上图按从左到右的连接顺序显示表。红色框表示 全表扫描 ,而绿色框表示使用 索引查找 。对于每个表, 显示使用的索引。还要注意的是,每个表格的框上方是每个表访问所发现的行数的估计值以及访问该表的成本。

5.2 SHOW WARNINGS的使用

在我们使用EXPLAIN语句查看了某个查询的执行计划后,紧接着还可以使用SHOW WARNINGS语句查看与这个查询的执行计划有关的一些扩展信息,比如这样:

sh 复制代码
mysql> EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;
sh 复制代码
mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
    Level: Note
     Code: 1003
Message: /* select#1 */ select `atguigu`.`s1`.`key1` AS `key1`,`atguigu`.`s2`.`key1`
AS `key1` from `atguigu`.`s1` join `atguigu`.`s2` where ((`atguigu`.`s1`.`key1` =
`atguigu`.`s2`.`key1`) and (`atguigu`.`s2`.`common_field` is not null))
1 row in set (0.00 sec)

大家可以看到SHOW WARNINGS展示出来的信息有三个字段,分别是Level、Code、Message。我们最常见的就是Code为1003的信息,当Code值为1003时,Message字段展示的信息类似于查询优化器将我们的查询语句重写后的语句。比如我们上边的查询本来是一个左(外)连接查询,但是有一个s2.common_field IS NOT NULL的条件,这就会导致查询优化器把左(外)连接查询优化为内连接查询,从SHOW WARNINGSMessage字段也可以看出来,原本的LEFE JOIN已经变成了JOIN。

但是大家一定要注意,我们说Message字段展示的信息类似于查询优化器将我们的查询语句重写后的语句,并不是等价于,也就是说Message字段展示的信息并不是标准的查询语句,在很多情况下并不能直接拿到黑框框中运行,它只能作为帮助我们理解MySQL将如何执行查询语句的一个参考依据而已。

6. 分析优化器执行计划:trace

mysql 复制代码
SET optimizer_trace="enabled=on",end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;

开启后,可分析如下语句:

  • SELECT
  • INSERT
  • REPLACE
  • UPDATE
  • DELETE
  • EXPLAIN
  • SET
  • DECLARE
  • CASE
  • IF
  • RETURN
  • CALL

测试:执行如下SQL语句

mysql 复制代码
select * from student where id < 10;

最后, 查询 information_schema.optimizer_trace 就可以知道MySQL是如何执行SQL的 :

mysql 复制代码
select * from information_schema.optimizer_trace\G
mysql 复制代码
*************************** 1. row ***************************
//第1部分:查询语句
QUERY: select * from student where id < 10
//第2部分:QUERY字段对应语句的跟踪信息
TRACE: {
"steps": [
{
    "join_preparation": { //预备工作
        "select#": 1,
        "steps": [
            {
            "expanded_query": "/* select#1 */ select `student`.`id` AS
            `id`,`student`.`stuno` AS `stuno`,`student`.`name` AS `name`,`student`.`age` AS
            `age`,`student`.`classId` AS `classId` from `student` where (`student`.`id` < 10)"
            }
        ] /* steps */
    } /* join_preparation */
},
{
    "join_optimization": { //进行优化
    "select#": 1,
    "steps": [
        {
        "condition_processing": { //条件处理
        "condition": "WHERE",
        "original_condition": "(`student`.`id` < 10)",
        "steps": [
        {
            "transformation": "equality_propagation",
            "resulting_condition": "(`student`.`id` < 10)"
        },
        {
            "transformation": "constant_propagation",
            "resulting_condition": "(`student`.`id` < 10)"
        },
        {
            "transformation": "trivial_condition_removal",
            "resulting_condition": "(`student`.`id` < 10)"
        }
        ] /* steps */
    } /* condition_processing */
    },
    {
        "substitute_generated_columns": { //替换生成的列
        } /* substitute_generated_columns */
    },
    {
        "table_dependencies": [ //表的依赖关系
        {
            "table": "`student`",
            "row_may_be_null": false,
            "map_bit": 0,
            "depends_on_map_bits": [
            ] /* depends_on_map_bits */
        }
    ] /* table_dependencies */
    },
    {
    "ref_optimizer_key_uses": [ //使用键
        ] /* ref_optimizer_key_uses */
        },
    {
        "rows_estimation": [ //行判断
        {
            "table": "`student`",
            "range_analysis": {
                "table_scan": {
                    "rows": 3973767,
                    "cost": 408558
            } /* table_scan */, //扫描表
            "potential_range_indexes": [ //潜在的范围索引
                {
                    "index": "PRIMARY",
                    "usable": true,
                    "key_parts": [
                    "id"
                    ] /* key_parts */
                }
            ] /* potential_range_indexes */,
        "setup_range_conditions": [ //设置范围条件
        ] /* setup_range_conditions */,
        "group_index_range": {
            "chosen": false,
            "cause": "not_group_by_or_distinct"
        } /* group_index_range */,
            "skip_scan_range": {
                "potential_skip_scan_indexes": [
                    {
                        "index": "PRIMARY",
                        "usable": false,
                        "cause": "query_references_nonkey_column"
                    }
                ] /* potential_skip_scan_indexes */
            } /* skip_scan_range */,
        "analyzing_range_alternatives": { //分析范围选项
            "range_scan_alternatives": [
                {
                "index": "PRIMARY",
                    "ranges": [
                        "id < 10"
                    ] /* ranges */,
                "index_dives_for_eq_ranges": true,
                "rowid_ordered": true,
                "using_mrr": false,
                "index_only": false,
                "rows": 9,
                "cost": 1.91986,
                "chosen": true
                }
            ] /* range_scan_alternatives */,
        "analyzing_roworder_intersect": {
            "usable": false,
            "cause": "too_few_roworder_scans"
        	} /* analyzing_roworder_intersect */
        } /* analyzing_range_alternatives */,
        "chosen_range_access_summary": { //选择范围访问摘要
            "range_access_plan": {
                "type": "range_scan",
                "index": "PRIMARY",
                "rows": 9,
                "ranges": [
                "id < 10"
                ] /* ranges */
                } /* range_access_plan */,
                "rows_for_plan": 9,
                "cost_for_plan": 1.91986,
                "chosen": true
                } /* chosen_range_access_summary */
                } /* range_analysis */
            }
        ] /* rows_estimation */
    },
    {
    "considered_execution_plans": [ //考虑执行计划
    {
    "plan_prefix": [
    ] /* plan_prefix */,
        "table": "`student`",
        "best_access_path": { //最佳访问路径
        "considered_access_paths": [
        {
            "rows_to_scan": 9,
            "access_type": "range",
            "range_details": {
            "used_index": "PRIMARY"
        } /* range_details */,
        "resulting_rows": 9,
        "cost": 2.81986,
        "chosen": true
    }
    ] /* considered_access_paths */
    } /* best_access_path */,
        "condition_filtering_pct": 100, //行过滤百分比
        "rows_for_plan": 9,
        "cost_for_plan": 2.81986,
        "chosen": true
    }
    ] /* considered_execution_plans */
    },
    {
        "attaching_conditions_to_tables": { //将条件附加到表上
        "original_condition": "(`student`.`id` < 10)",
        "attached_conditions_computation": [
        ] /* attached_conditions_computation */,
        "attached_conditions_summary": [ //附加条件概要
    {
        "table": "`student`",
        "attached": "(`student`.`id` < 10)"
    }
    ] /* attached_conditions_summary */
    } /* attaching_conditions_to_tables */
    },
    {
    "finalizing_table_conditions": [
    {
        "table": "`student`",
        "original_table_condition": "(`student`.`id` < 10)",
        "final_table_condition ": "(`student`.`id` < 10)"
    }
    ] /* finalizing_table_conditions */
    },
    {
    "refine_plan": [ //精简计划
    {
    	"table": "`student`"
    }
    ] /* refine_plan */
    }
    ] /* steps */
    } /* join_optimization */
},
	{
        "join_execution": { //执行
            "select#": 1,
            "steps": [
            ] /* steps */
        	} /* join_execution */
        }
    ] /* steps */
}
//第3部分:跟踪信息过长时,被截断的跟踪信息的字节数。
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0 //丢失的超出最大容量的字节
//第4部分:执行跟踪语句的用户是否有查看对象的权限。当不具有权限时,该列信息为1且TRACE字段为空,一般在
调用带有SQL SECURITY DEFINER的视图或者是存储过程的情况下,会出现此问题。
INSUFFICIENT_PRIVILEGES: 0 //缺失权限
1 row in set (0.00 sec)

7. MySQL监控分析视图-sys schema

7.1 Sys schema 视图摘要

  1. 主机相关:以host_summary开头,主要汇总了IO延迟的信息。
  2. Innodb相关:以innodb开头,汇总了innodb buffer信息和事务等待innodb锁的信息。
  3. I/o相关:以io开头,汇总了等待I/O、I/O使用量情况。
  4. 内存使用情况:以memory开头,从主机、线程、事件等角度展示内存的使用情况
  5. 连接与会话信息:processlist和session相关视图,总结了会话相关信息。
  6. 表相关:以schema_table开头的视图,展示了表的统计信息。
  7. 索引信息:统计了索引的使用情况,包含冗余索引和未使用的索引情况。
  8. 语句相关:以statement开头,包含执行全表扫描、使用临时表、排序等的语句信息。
  9. 用户相关:以user开头的视图,统计了用户使用的文件I/O、执行语句统计信息。
  10. 等待事件相关信息:以wait开头,展示等待事件的延迟情况。

7.2 Sys schema视图使用场景

索引情况

mysql 复制代码
#1. 查询冗余索引
select * from sys.schema_redundant_indexes;
#2. 查询未使用过的索引
select * from sys.schema_unused_indexes;
#3. 查询索引的使用情况
select index_name,rows_selected,rows_inserted,rows_updated,rows_deleted
from sys.schema_index_statistics where table_schema='dbname';

表相关

mysql 复制代码
# 1. 查询表的访问量
select table_schema,table_name,sum(io_read_requests+io_write_requests) as io from
sys.schema_table_statistics group by table_schema,table_name order by io desc;
# 2. 查询占用bufferpool较多的表
select object_schema,object_name,allocated,data
from sys.innodb_buffer_stats_by_table order by allocated limit 10;
# 3. 查看表的全表扫描情况
select * from sys.statements_with_full_table_scans where db='dbname';

语句相关

mysql 复制代码
#1. 监控SQL执行的频率
select db,exec_count,query from sys.statement_analysis
order by exec_count desc;
#2. 监控使用了排序的SQL
select db,exec_count,first_seen,last_seen,query
from sys.statements_with_sorting limit 1;
#3. 监控使用了临时表或者磁盘临时表的SQL
select db,exec_count,tmp_tables,tmp_disk_tables,query
from sys.statement_analysis where tmp_tables>0 or tmp_disk_tables >0
order by (tmp_tables+tmp_disk_tables) desc;

IO相关

mysql 复制代码
#1. 查看消耗磁盘IO的文件
select file,avg_read,avg_write,avg_read+avg_write as avg_io
from sys.io_global_by_file_by_bytes order by avg_read limit 10;

Innodb 相关

mysql 复制代码
#1. 行锁阻塞情况
select * from sys.innodb_lock_waits;

8. 小结:

查询是数据库中最频繁的操作,提高查询速度可以有效地提高MySQL数据库的性能。通过对查询语句的分析可以了解查询语句的执行情况,找出查询语句执行的瓶颈,从而优化查询语句。

附件:测试 + 对应学习 SQL 脚本内容

mysql 复制代码
USE dbtest1;

#1. table:表名
#查询的每一行记录都对应着一个单表
EXPLAIN SELECT * FROM s1;

#s1:驱动表  s2:被驱动表
EXPLAIN SELECT * FROM s1 INNER JOIN s2;

#2. id:在一个大的查询语句中每个SELECT关键字都对应一个唯一的id
 SELECT * FROM s1 WHERE key1 = 'a';


 SELECT * FROM s1 INNER JOIN s2
 ON s1.key1 = s2.key1
 WHERE s1.common_field = 'a';


 SELECT * FROM s1 
 WHERE key1 IN (SELECT key3 FROM s2);


 SELECT * FROM s1 UNION SELECT * FROM s2;


 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
 
 
 EXPLAIN SELECT * FROM s1 INNER JOIN s2;
 
 
 EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
 
 ######查询优化器可能对涉及子查询的查询语句进行重写,转变为多表查询的操作########
 EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field = 'a');
 
 #Union去重
 EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
 
 
 EXPLAIN SELECT * FROM s1  UNION ALL SELECT * FROM s2;
 
 
 #3. select_type:SELECT关键字对应的那个查询的类型,确定小查询在整个大查询中扮演了一个什么角色
 
 # 查询语句中不包含`UNION`或者子查询的查询都算作是`SIMPLE`类型
 EXPLAIN SELECT * FROM s1;
 
 
 #连接查询也算是`SIMPLE`类型
 EXPLAIN SELECT * FROM s1 INNER JOIN s2;
 
 
 #对于包含`UNION`或者`UNION ALL`或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个
 #查询的`select_type`值就是`PRIMARY`
 
 
 #对于包含`UNION`或者`UNION ALL`的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询
 #以外,其余的小查询的`select_type`值就是`UNION`
 
 #`MySQL`选择使用临时表来完成`UNION`查询的去重工作,针对该临时表的查询的`select_type`就是
 #`UNION RESULT`
 EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
 
 EXPLAIN SELECT * FROM s1 UNION ALL SELECT * FROM s2;
 
 #子查询:
 #如果包含子查询的查询语句不能够转为对应的`semi-join`的形式,并且该子查询是不相关子查询。
 #该子查询的第一个`SELECT`关键字代表的那个查询的`select_type`就是`SUBQUERY`
 EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
 
 
 #如果包含子查询的查询语句不能够转为对应的`semi-join`的形式,并且该子查询是相关子查询,
 #则该子查询的第一个`SELECT`关键字代表的那个查询的`select_type`就是`DEPENDENT SUBQUERY`
 EXPLAIN SELECT * FROM s1 
 WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 = s2.key2) OR key3 = 'a';
 #注意的是,select_type为`DEPENDENT SUBQUERY`的查询可能会被执行多次。
 
 
 #在包含`UNION`或者`UNION ALL`的大查询中,如果各个小查询都依赖于外层查询的话,那除了
 #最左边的那个小查询之外,其余的小查询的`select_type`的值就是`DEPENDENT UNION`。
 EXPLAIN SELECT * FROM s1 
 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');
 
 
 #对于包含`派生表`的查询,该派生表对应的子查询的`select_type`就是`DERIVED`
 EXPLAIN SELECT * 
 FROM (SELECT key1, COUNT(*) AS c FROM s1 GROUP BY key1) AS derived_s1 WHERE c > 1;
 
 
 #当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,
 #该子查询对应的`select_type`属性就是`MATERIALIZED`
 EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2); #子查询被转为了物化表
 
 
 
 # 4. partition(略):匹配的分区信息
 
 
 # 5. type:针对单表的访问方法
 
 #当表中`只有一条记录`并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,
 #那么对该表的访问方法就是`system`。
 CREATE TABLE t(i INT) ENGINE=MYISAM;
 INSERT INTO t VALUES(1);
 
 EXPLAIN SELECT * FROM t;
 
 #换成InnoDB
 CREATE TABLE tt(i INT) ENGINE=INNODB;
 INSERT INTO tt VALUES(1);
 EXPLAIN SELECT * FROM tt;
 
 
 #当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是`const`
 EXPLAIN SELECT * FROM s1 WHERE id = 10005;
 
 EXPLAIN SELECT * FROM s1 WHERE key2 = 10066;
 
 
 #在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的
 #(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则
 #对该被驱动表的访问方法就是`eq_ref`
 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
  
  
 #当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是`ref`
 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
 
 
 #当对普通二级索引进行等值匹配查询,该索引列的值也可以是`NULL`值时,那么对该表的访问方法
 #就可能是`ref_or_null`
 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL;
 
 
 #单表访问方法时在某些场景下可以使用`Intersection`、`Union`、
 #`Sort-Union`这三种索引合并的方式来执行查询
 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
 
 
 #`unique_subquery`是针对在一些包含`IN`子查询的查询语句中,如果查询优化器决定将`IN`子查询
 #转换为`EXISTS`子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的`type`
 #列的值就是`unique_subquery`
 EXPLAIN SELECT * FROM s1 
 WHERE key2 IN (SELECT id FROM s2 WHERE s1.key1 = s2.key1) OR key3 = 'a';
 
 
 #如果使用索引获取某些`范围区间`的记录,那么就可能使用到`range`访问方法
 EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');
 
 #同上
 EXPLAIN SELECT * FROM s1 WHERE key1 > 'a' AND key1 < 'b';
 
 
 #当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是`index`
 EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a';
 
 
 #最熟悉的全表扫描
 EXPLAIN SELECT * FROM s1;
 
 
 #6. possible_keys和key:可能用到的索引 和  实际上使用的索引
 
 EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';
 

 
#7.  key_len:实际使用到的索引长度(即:字节数)
# 帮你检查`是否充分的利用上了索引`,`值越大越好`,主要针对于联合索引,有一定的参考意义。
 EXPLAIN SELECT * FROM s1 WHERE id = 10005;


 EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;


 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';


 EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a';

 
 EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';

 EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c';
 
 EXPLAIN SELECT * FROM s1 WHERE key_part3 = 'a';
 
#练习:
#varchar(10)变长字段且允许NULL  = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)

#varchar(10)变长字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+2(变长字段)

#char(10)固定字段且允许NULL    = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)

#char(10)固定字段且不允许NULL  = 10 * ( character set:utf8=3,gbk=2,latin1=1)
 
 
 
 # 8. ref:当使用索引列等值查询时,与索引列进行等值匹配的对象信息。
 #比如只是一个常数或者是某个列。
 
 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
 
 
 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
 
 
 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);
 
 
 # 9. rows:预估的需要读取的记录条数
 # `值越小越好`
 EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';
 
 
 
 # 10. filtered: 某个表经过搜索条件过滤后剩余记录条数的百分比
 
 #如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用
 #到对应索引的搜索条件外的其他搜索条件的记录有多少条。
 EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND common_field = 'a';
 
 
 #对于单表查询来说,这个filtered列的值没什么意义,我们`更关注在连接查询
 #中驱动表对应的执行计划记录的filtered值`,它决定了被驱动表要执行的次数(即:rows * filtered)
 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';
 
 
 #11. Extra:一些额外的信息
 #更准确的理解MySQL到底将如何执行给定的查询语句
 
 
 #当查询语句的没有`FROM`子句时将会提示该额外信息
 EXPLAIN SELECT 1;
 
 
 #查询语句的`WHERE`子句永远为`FALSE`时将会提示该额外信息
 EXPLAIN SELECT * FROM s1 WHERE 1 != 1;
 
 
 #当我们使用全表扫描来执行对某个表的查询,并且该语句的`WHERE`
 #子句中有针对该表的搜索条件时,在`Extra`列中会提示上述额外信息。
 EXPLAIN SELECT * FROM s1 WHERE common_field = 'a';
 
 
 #当使用索引访问来执行对某个表的查询,并且该语句的`WHERE`子句中
 #有除了该索引包含的列之外的其他搜索条件时,在`Extra`列中也会提示上述额外信息。
 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND common_field = 'a';
 
 
 #当查询列表处有`MIN`或者`MAX`聚合函数,但是并没有符合`WHERE`子句中
 #的搜索条件的记录时,将会提示该额外信息
 EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg';
 
 EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'NlPros'; #NlPros 是 s1表中key1字段真实存在的数据
 
 #select * from s1 limit 10;
 
 #当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以
 #使用覆盖索引的情况下,在`Extra`列将会提示该额外信息。比方说下边这个查询中只
 #需要用到`idx_key1`而不需要回表操作:
 EXPLAIN SELECT key1,id FROM s1 WHERE key1 = 'a';
 
 
 #有些搜索条件中虽然出现了索引列,但却不能使用到索引
 #看课件理解索引条件下推
 EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a';
 
 
 #在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为
 #其分配一块名叫`join buffer`的内存块来加快查询速度,也就是我们所讲的`基于块的嵌套循环算法`
 #见课件说明
 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field;
 
 
 #当我们使用左(外)连接时,如果`WHERE`子句中包含要求被驱动表的某个列等于`NULL`值的搜索条件,
 #而且那个列又是不允许存储`NULL`值的,那么在该表的执行计划的Extra列就会提示`Not exists`额外信息
 EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;
 
 
 #如果执行计划的`Extra`列出现了`Using intersect(...)`提示,说明准备使用`Intersect`索引
 #合并的方式执行查询,括号中的`...`表示需要进行索引合并的索引名称;
 #如果出现了`Using union(...)`提示,说明准备使用`Union`索引合并的方式执行查询;
 #出现了`Using sort_union(...)`提示,说明准备使用`Sort-Union`索引合并的方式执行查询。
 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
 
 
 #当我们的`LIMIT`子句的参数为`0`时,表示压根儿不打算从表中读出任何记录,将会提示该额外信息
 EXPLAIN SELECT * FROM s1 LIMIT 0;
 
 
 #有一些情况下对结果集中的记录进行排序是可以使用到索引的。
 #比如:
 EXPLAIN SELECT * FROM s1 ORDER BY key1 LIMIT 10;
 
 
 #很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)
 #进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序(英文名:`filesort`)。
 
 #如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的`Extra`列中显示`Using filesort`提示
 EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10;
 
 
 #在许多查询的执行过程中,MySQL可能会借助临时表来完成一些功能,比如去重、排序之类的,比如我们
 #在执行许多包含`DISTINCT`、`GROUP BY`、`UNION`等子句的查询过程中,如果不能有效利用索引来完成
 #查询,MySQL很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行
 #计划的`Extra`列将会显示`Using temporary`提示
 EXPLAIN SELECT DISTINCT common_field FROM s1;
 
 #EXPLAIN SELECT DISTINCT key1 FROM s1;
 
 #同上。
 EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field;
 
 #执行计划中出现`Using temporary`并不是一个好的征兆,因为建立与维护临时表要付出很大成本的,所以
 #我们`最好能使用索引来替代掉使用临时表`。比如:扫描指定的索引idx_key1即可
 EXPLAIN SELECT key1, COUNT(*) AS amount FROM s1 GROUP BY key1;
 
#json格式的explain
EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 
WHERE s1.common_field = 'a';
 

最后:

"在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。"

相关推荐
Sylvia-girl1 分钟前
线程通讯~
java
Tangcan-3 分钟前
【Redis】通用命令 1
数据库·redis·缓存
禹凕4 分钟前
Python编程——进阶知识(MYSQL引导入门)
开发语言·python·mysql
Victor3564 分钟前
MongoDB(2)MongoDB与传统关系型数据库的主要区别是什么?
后端
JaguarJack4 分钟前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端·php·服务端
BingoGo5 分钟前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端
Victor3566 分钟前
MongoDB(3)什么是文档(Document)?
后端
MSTcheng.7 分钟前
【C++】C++异常
java·数据库·c++·异常
草莓熊Lotso1 小时前
Linux 文件描述符与重定向实战:从原理到 minishell 实现
android·linux·运维·服务器·数据库·c++·人工智能
大模型玩家七七1 小时前
基于语义切分 vs 基于结构切分的实际差异
java·开发语言·数据库·安全·batch