Chapitre Avancé MySQL - Instruction de requête d'analyse EXPLAIN

Annuaire d'articles :

1. À propos d'EXPLIQUER

2. Préparation des données

3. Le rôle de chaque colonne dans EXPLAIN

3.1 tableau

3.2 identifiant

3.3 select_type

3.4 partition (omise) : informations de partition correspondantes

Type 3.5

3.6 possible_keys和clé

3.7 key_len

3.8 réf

3,9 rangées

3.10 filtré

3.11 Supplémentaire


1. À propos d'EXPLIQUER

Présentation du site officiel
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
État des versions
Avant MySQL 5.6.3 , uniquement EXPLAIN SELECT ; après MYSQL 5.6.3 , vous pouvez EXPLAIN SELECT , UPDATE , DELETE
Dans les versions antérieures à 5.7 , pour afficher les partitions , utilisez la commande EXPLAIN PARTITIONS ; pour afficher filtered , utilisez la commande EXPLAIN EXTENSIBLE. Après la version 5.7 , l' explication par défaut affiche directement les informations dans les partitions et filtrées.
Les fonctions de chaque sortie de colonne par l'instruction EXPLAIN sont les suivantes :


2. Préparation des données

#创建表
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. Le rôle de chaque colonne dans EXPLAIN

3.1 tableau

Quelle que soit la complexité de notre instruction de requête et le nombre de tables qu'elle contient , il est finalement nécessaire d'effectuer un accès à une seule . MySQL stipule donc que chaque enregistrement généré par l' instruction EXPLAIN correspond à l'accès de une méthode de table unique, la colonne de table de l'enregistrement représente le nom de table de la table (parfois pas le vrai nom de table, il peut s'agir d'une abréviation).
#1. table:表名
#查询的每一行记录都对应着一个单表
EXPLAIN SELECT * FROM s1;

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

3.2 identifiant

Si l' identifiant est le même, il peut être considéré comme un groupe et exécuté dans l'ordre de haut en bas
Dans tous les groupes, plus la valeur de l' identifiant est élevée, plus la priorité est élevée et le premier à exécuter
Préoccupations : Chaque numéro du numéro d' identification représente une requête indépendante, moins il y a de requêtes d' un SQL , mieux c'est
#2. id:在一个大的查询语句中每个SELECT关键字都对应一个唯一的id
EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';

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

Cela implique l'union et la déduplication de UNION, c'est-à-dire qu'après l'union des deux tables, il y aura des données en double et une table temporaire apparaîtra, il y a donc trois identifiants au total.

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

Il y a deux SELECT ici, et UNION ALL n'implique pas de déduplication, donc il n'y a pas de table temporaire, et les identifiants des deux SELECT sont 1 et 2 respectivement. 

EXPLAIN SELECT * FROM s1 INNER JOIN s2;

Deux tables sont impliquées ici, mais à la fin il n'y a qu'un seul SELECT, donc les deux identifiants d'enregistrement sont 1. 

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

Cela implique une sous-requête, qui a une priorité plus élevée, il y a donc deux identifiants (1 et 2).

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

3.3 select_type

select_type : le type de requête correspondant au mot-clé SELECT, qui détermine le rôle joué par la petite requête dans l'ensemble de la grande requête 

# 查询语句中不包含`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 (omise) : informations de partition correspondantes

Type 3.5

Les méthodes d'accès complet sont les suivantes : system , const , eq_ref , ref , fulltext , ref_or_null , index_merge , unique_subquery , index_subquery , range , index , ALL .
Les valeurs résultantes, du meilleur au pire, sont : system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL Les plus importants sont extraits (voir le bleu dans la figure ci-dessus) ) . L'objectif de l'optimisation des performances SQL  : atteindre au moins le niveau de la plage , l'exigence est le niveau ref , de préférence le niveau consts . (Alibaba
manuel de développement)
#当表中`只有一条记录`并且该表使用的存储引擎的统计数据是精确的,比如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_keysclé

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

Puisque nous avons créé des index sur les champs key1 et key3 de la table s1, il peut être utilisé pour les deux ici, mais l'opération réelle peut voir que MySQL a finalement choisi l'index en utilisant le champ key3. 

3.7 key_len

key_len : La longueur réelle de l'index utilisé (c'est-à-dire : le nombre d'octets)
# Vous aide à vérifier si l'index est entièrement utilisé.

EXPLAIN SELECT * FROM s1 WHERE id = 10005;

Ici, la clé primaire de la table s1 est id, et le type INT occupe 4 octets, donc la longueur est de 4. 

EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;

Pour le champ key2 ici, nous définissons également le type INT dans l'instruction de construction de table ci-dessus. Pourquoi n'est-il pas 4, mais 5 ? Étant donné que l'identifiant ci-dessus est la clé primaire, il est naturellement non nul. La clé2 ici est juste un simple champ de type INT, plus un index unique, ce qui signifie qu'il peut être NULL, donc un 1 supplémentaire est plus à la place NULL.

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

En utf8, en effet, un caractère occupe 3 octets par défaut, alors key1 vaut VARCHAR(100), soit 100*3 = 300. Quant à 303, il y en a 3 de plus, ce qui veut dire que key1 peut aussi être NULL, donc 300 +1=301, et comme il s'agit d'un type de longueur variable VARCHAR, deux octets sont nécessaires pour enregistrer la longueur variable, c'est-à-dire 300+1+2=303.

Les 303, 606, 909 suivants sont les mêmes.

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

Dans ce cas, parce que nous avons construit un index conjoint de key_part1, key_part2 et key_part3, et ici nous filtrons directement la requête en fonction de key_part3, et ne suivons pas le principe du préfixe le plus à gauche, donc l'index n'est pas utilisé du tout, laissez seul la longueur de l'index key_len.Donc null. 

#Exercice :
#varchar(10) champ de longueur variable et autoriser NULL = 10 * (jeu de caractères : utf8=3, gbk=2, latin1=1) + 1(NULL) + 2(champ de longueur variable)

#varchar(10) Champ de longueur variable et n'autorise pas NULL = 10 * (jeu de caractères : utf8=3, gBK=2, latin1=1) + 2 (champ de longueur variable)

#char(10) champ fixe et autoriser NULL = 10 * (jeu de caractères : utf8=3, gbk=2, latin1=1) + 1(NULL)

#char(10) champ fixe et interdit NULL = 10 * (jeu de caractères : utf8=3, gbk=2, latin1=1)

3.8 réf

ref : lors de l'utilisation de la requête de valeur égale de la colonne d'index, les informations d'objet pour une correspondance de valeur égale avec la colonne d'index.
#Par exemple, juste une constante ou une colonne.

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 rangées

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

3.10  filtré

filtré : le pourcentage d'enregistrements restants après qu'une table a été filtrée par des critères de recherche.

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

Pour la dernière pièce, je joindrai directement le code, et vous pourrez exécuter les captures d'écran par vous-même, et je ne téléchargerai plus les captures d'écran.

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

Je suppose que tu aimes

Origine blog.csdn.net/weixin_43823808/article/details/124147428
conseillé
Classement