MySQL Advanced Chapter - EXPLAIN Analysis Query Statement

Article directory:

1. About EXPLAIN

2. Data preparation

3. The role of each column in EXPLAIN

3.1 table

3.2 id

3.3 select_type

3.4 partition (omitted): matching partition information

3.5 type

3.6 possible_keys和key

3.7 key_len

3.8 ref

3.9 rows

3.10 filtered

3.11 Extra


1. About EXPLAIN

Official website introduction
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
Version situation
Before MySQL 5.6.3 , only EXPLAIN SELECT ; after MYSQL 5.6.3 , you can EXPLAIN SELECT , UPDATE , DELETE
In versions prior to 5.7 , to display partitions , use the explain partitions command; to display filtered , use the explain extended command. After version 5.7 , the default explain directly displays the information in partitions and filtered.
The functions of each column output by the EXPLAIN statement are as follows:


2. Data preparation

#创建表
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;


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;

#创建存储函数:
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 ;

SET GLOBAL log_bin_trust_function_creators=1; 

#创建存储过程:
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 ;


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 ;

#调用存储过程
CALL insert_s1(10001,10000);

CALL insert_s2(10001,10000);

SELECT COUNT(*) FROM s1;

SELECT COUNT(*) FROM s2;

3. The role of each column in EXPLAIN

3.1 table

No matter how complex our query statement is, and how many tables are included in it , in the end it is necessary to perform single- , so MySQL stipulates that each record output by the EXPLAIN statement corresponds to the access of a single table method , the table column of the record represents the table name of the table (sometimes not the real table name, it may be an abbreviation).
#1. table:表名
#查询的每一行记录都对应着一个单表
EXPLAIN SELECT * FROM s1;

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

3.2 id

If the id is the same, it can be considered as a group and executed in order from top to bottom
In all groups, the larger the id value, the higher the priority and the first to execute
Concerns: Each number of the id number represents an independent query, the fewer the number of queries of a SQL , the better
#2. id:在一个大的查询语句中每个SELECT关键字都对应一个唯一的id
EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';

#Union去重
EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

This involves the problem of union and deduplication, that is, after the union of two tables, there will be some duplicate data, and a temporary table will appear, so there are three ids.

EXPLAIN SELECT * FROM s1 UNION ALL SELECT * FROM s2;

There are two SELECTs here, and UNION ALL does not involve deduplication, so there is no temporary table, and the ids of the two SELECTs are 1 and 2 respectively. 

EXPLAIN SELECT * FROM s1 INNER JOIN s2;

Two tables are involved here, but in the end there is only one SELECT, so both record ids are 1. 

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';

This involves a subquery, which has a higher priority, so there are two ids (1 and 2).

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

3.3 select_type

select_type: The type of the query corresponding to the SELECT keyword, which determines what role the small query plays in the entire large query 

# 查询语句中不包含`UNION`或者子查询的查询都算作是`SIMPLE`类型
EXPLAIN SELECT * FROM s1;

#连接查询也算是`SIMPLE`类型
EXPLAIN SELECT * FROM s1 INNER JOIN s2;

#对于包含`UNION`或者`UNION ALL`或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的`select_type`值就是`PRIMARY`,其中除了最左边的那个小查询
#以外,其余的小查询的`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); #子查询被转为了物化表

3.4 partition (omitted): matching partition information

3.5 type

The full access methods are as follows: system , const , eq_ref , ref , fulltext , ref_or_null , index_merge , unique_subquery , index_subquery , range , index , ALL .
The resulting values, from best to worst, are: system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL The more important ones are extracted (see the blue ). The goal of SQL performance optimization: at least reach the range level, the requirement is the ref level, preferably the consts level. (Alibaba
development manual)
#当表中`只有一条记录`并且该表使用的存储引擎的统计数据是精确的,比如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;

3.6 possible_keyskey

#6. possible_keys和key:可能用到的索引 和  实际上使用的索引
EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';

Since we have created indexes on both the key1 and key3 fields of the s1 table, it may be used for both here, but the actual operation can see that MySQL finally chose the index using the key3 field. 

3.7 key_len

key_len: The actual length of the index used (ie: number of bytes)
# Help you to check whether the index is fully utilized.

EXPLAIN SELECT * FROM s1 WHERE id = 10005;

Here, the primary key of the s1 table is id, and the INT type occupies 4 bytes, so the length is 4. 

EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;

For the key2 field here, we also set the INT type in the above table building statement. Why is it not 4, but 5? Because the above id is the primary key, it is naturally non-null. The key2 here is just a simple INT type field, plus a unique index, that is to say, it can be NULL, so more 1 is more in the NULL place.

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

In utf8, in fact, a character occupies 3 bytes by default, then key1 is VARCHAR(100), which is 100*3=300. As for 303, there are 3 more, which means that key1 may also be NULL, so 300+1=301, and because it is a VARCHAR variable-length type, two bytes are needed here to record the variable length, that is, 300+1+2=303.

The following 303, 606, 909 are the same.

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';

In this case, because we built a joint index of key_part1, key_part2, and key_part3, and here we directly filter the query based on key_part3, and do not follow the principle of the leftmost prefix, so the index is not used at all, let alone the length of the key_len index. So null. 

#Exercise :
#varchar(10) variable length field and allow NULL = 10 * ( character set: utf8=3, gbk=2, latin1=1) + 1(NULL) + 2(variable length field)

#varchar(10) Variable length field and does not allow NULL = 10 * ( character set: utf8=3, gbk=2, latin1=1) + 2 (variable length field)

#char(10) fixed field and allow NULL = 10 * ( character set: utf8=3, gbk=2, latin1=1) + 1(NULL)

#char(10) fixed field and disallow NULL = 10 * ( character set: utf8=3, gbk=2, latin1=1)

3.8 ref

ref: When using the index column equal value query, the object information for equal value matching with the index column.
#For example, just a constant or a column.

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);

3.9 rows

# rows:预估的需要读取的记录条数
# `值越小越好`
EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';

3.10 filtered

filtered: The percentage of remaining records after a table is filtered by search criteria.

#如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用
#到对应索引的搜索条件外的其他搜索条件的记录有多少条。
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';

3.11 Extra

For the last piece, I will directly attach the code, and you can run the screenshots by yourself, and I will not upload the screenshots anymore.

#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';

Guess you like

Origin blog.csdn.net/weixin_43823808/article/details/124147428
Recommended