MySQL_Explain详解

发布时间 2023-07-28 11:33:25作者: 没那么简单pq
  当我们在工作中面临SQL优化的问题时,熟练掌握适合的工具,就能使事半功倍,提高工作效率。其中,EXPLAIN工具就是一种常用且高效的SQL优化工具。
  EXPLAIN关键字的使用方法是,在select语句之前添加它,这样MySQL会在查询上设置一个标记。但不同于普通查询,此时执行的并不是查询语句本身,而是返回了执行计划的信息。这样,我们就可以模拟优化器执行SQL语句,深入剖析查询语句或结构的性能瓶颈。
简单来说,EXPLAIN工具就像一面镜子,让我们看到SQL查询语句的内在运行逻辑,有助于我们找出并修复性能问题,从而实现SQL的优化。
对于广大公众来说,EXPLAIN工具的使用,将成为他们提升SQL性能的强有力的助手。让我们一起学习和掌握它,优化我们的SQL查询,提升工作效率。
为了方便大家能够学以致用,学有所练,在这里提供给大家原始的建表语句和思维导图总结,

MYSQL_01Explain详解与索引实践 思维导图模板_ProcessOn思维导图、流程图
https://www.processon.com/view/629d5405e0b34d3bc3b28f04

 

DROP TABLE IF EXISTS `departments`;
CREATE TABLE `departments`  (
  `id` int(0) NOT NULL,
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of departments
-- ----------------------------
INSERT INTO `departments` VALUES (1, 'Human Resources');
INSERT INTO `departments` VALUES (2, 'Marketing');
INSERT INTO `departments` VALUES (3, 'Finance');
​
DROP TABLE IF EXISTS `departments_employees`;
CREATE TABLE `departments_employees`  (
  `id` int(0) NOT NULL,
  `d_id` int(0) NOT NULL,
  `e_id` int(0) NOT NULL,
  `remark` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `num` int(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `idx_d_e_id`(`d_id`, `e_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of departments_employees
-- ----------------------------
INSERT INTO `departments_employees` VALUES (1, 1, 1, NULL, NULL);
INSERT INTO `departments_employees` VALUES (2, 1, 2, NULL, NULL);
INSERT INTO `departments_employees` VALUES (3, 2, 1, NULL, NULL);
​
DROP TABLE IF EXISTS `employees`;
CREATE TABLE `employees`  (
  `id` int(0) NOT NULL,
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `salary` decimal(10, 2) NULL DEFAULT NULL,
  `department_id` int(0) NULL DEFAULT NULL,
  `nums` bigint(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `department_id`(`department_id`) USING BTREE,
  INDEX `salary`(`salary`) USING BTREE,
  INDEX `name_sal_did`(`name`, `salary`, `department_id`) USING BTREE,
  INDEX `name_did`(`name`, `department_id`) USING BTREE,
  CONSTRAINT `employees_ibfk_1` FOREIGN KEY (`department_id`) REFERENCES `departments` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of employees
-- ----------------------------
INSERT INTO `employees` VALUES (1, 'Alice', 70000.00, 1, NULL);
INSERT INTO `employees` VALUES (2, 'Bob', 80000.00, 1, NULL);
INSERT INTO `employees` VALUES (3, 'Charlie', 90000.00, 2, NULL);
INSERT INTO `employees` VALUES (4, 'Dave', 100000.00, 2, NULL);
INSERT INTO `employees` VALUES (5, 'Eve', 110000.00, 3, NULL);
INSERT INTO `employees` VALUES (6, 'Frank', 120000.00, 3, NULL);
​
​

 

Explain的各个列详解析

id:select查询的标识符,表示查询的顺序。

EXPLAIN SELECT * FROM EMPLOYEES E1 JOIN DEPARTMENTS E2 ON E1.DEPARTMENT_ID = E2.ID ;
0
e1表的id为1,e2表的id为2,那么代表先查询e1表,然后再查询e2表。
注意事项:id列越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。如果id相同,表示是同级别的,执行顺序由MySQL优化器来决定。
 

select_type:用于标识查询中的SELECT语句的类型,

MySQL的查询优化器有一个特性叫做派生表合并(Derived Table Merge),这个特性在MySQL 5.7及以上版本默认是开启的。 
开启这个特性后,MySQL查询优化器会尝试将派生表(即子查询生成的临时表)合并到主查询中,以便能够更高效地处理查询。
如果你想在EXPLAIN的输出中看到派生表,你可以通过设置会话变量optimizer_switch来关闭派生表合并特性,

SET SESSION optimizer_switch='derived_merge=off';

 

SIMPLE:如果查询中不包含子查询或UNION,则select_type是SIMPLE,简单查询
EXPLAIN SELECT NAME FROM EMPLOYEES WHERE SALARY > 80000;
0
 
PRIMARY:查询中如果包含任何复杂的子部分,那么最外层的查询被标记为PRIMARY。在所有的查询中,如果没有使用UNION或子查询,select_type就是PRIMARY。
 
EXPLAIN SELECT D.NAME FROM DEPARTMENTS D WHERE D.ID IN (SELECT DEPARTMENT_ID FROM EMPLOYEES WHERE SALARY > 80000); 
按理来说,在这个查询中,外部的查询是PRIMARY。在这种情况下,MySQL 会执行一个称为 "子查询关联" 的优化。对于 IN 子查询,MySQL 会尝试重写它们为 INNER JOIN, 如果可以重写,MySQL 就会优化执行计划。所以才看到两个 SELECT 都被标记为 SIMPLE 的原因。MySQL 实际上将子查询转换成了一个简单的 JOIN 查询。 但是请注意,这并不是所有情况下都会发生。这取决于查询优化器如何判断可以获得最好的性能。 有时,为了禁止子查询优化,可以使用 STRAIGHT_JOIN 来强制 MySQL 按照你指定的 JOIN 顺序执行查询,在大多数情况下,MySQL的查询优化器可以找出最优的执行顺序。 所以,我们应该谨慎使用STRAIGHT_JOIN关键字,只有在确定默认的执行顺序不理想时,才应该考虑使用它。
 
SUBQUERY:在主查询中出现的子查询被标记为SUBQUERY。
例如,查询薪水大于80000的员工所在的部门名称,和上面的例子一样,但是这里我们将内部查询视为子查询:
EXPLAIN SELECT d.name FROM departments d WHERE d.id IN (SELECT department_id FROM employees WHERE salary > 80000);
在这个查询中,内部的查询就被标记为SUBQUERY。
 
UNION:如果在UNION操作中的SELECT不是第一个SELECT,那么这个SELECT被标记为UNION。
例如,查询所有在Human Resources部门工作,或者薪水大于80000的员工的名字:
EXPLAIN SELECT name FROM employees WHERE department_id = (SELECT id FROM departments WHERE name = 'Human Resources') UNION SELECT name FROM employees WHERE salary > 80000;
在这个查询中,第二个SELECT就被标记为UNION。
0
 
UNION RESULT:这个SELECT从由UNION操作生成的表中检索结果。
例如,将上面的UNION查询包裹在一个外部查询中:
EXPLAIN SELECT * FROM (SELECT name FROM employees WHERE department_id = (SELECT id FROM departments WHERE name = 'Human Resources') UNION SELECT name FROM employees WHERE salary > 80000) AS unioned;
在这个查询中,最外层的查询就被标记为UNION RESULT。
0
 
DERIVED:在FROM列表中包含的子查询会被标记为DERIVED。MySQL会为这种子查询创建一个临时表。
EXPLAIN SELECT * FROM (SELECT id FROM departments WHERE name = 'Human Resources') AS derived_table;
在这个查询中,子查询就会被标记为DERIVED。MySQL会首先执行这个子查询,然后将结果存储在一个临时表中,这个临时表就叫做"derived_table"。
0
 

 

table:输出结果集的表。

table列显示了查询中正在访问的表的名称。在处理JOIN操作时,table列可以帮助你理解MySQL以什么样的顺序来访问各个表。
需要注意的事项如下:
JOIN顺序: table列显示的顺序可以帮助你理解MySQL如何执行JOIN操作。如果你的查询中有多个表,那么这个顺序可能会对查询性能产生很大影响。例如,如果一个大表和一个小表进行JOIN操作,通常最好先扫描小表,这样可能会更快。
 
别名: 如果你在查询中给表使用了别名,那么EXPLAIN的输出结果将会使用这个别名。别名可以帮助你在查询中更清楚地识别不同的表。
 
派生表: 如果你的查询中有子查询,那么EXPLAIN的输出可能会显示一个不真实存在的表名,这其实是一个派生表(Derived Table)。派生表是MySQL在处理子查询时内部生成的临时表。
 
临时表和文件排序: 当MySQL需要使用临时表或者文件排序时,table列可能会显示, <union1,2>, <sort_union>, , 等信息。这些信息表示MySQL在执行查询时需要额外的操作。
 

type:连接类型,常见的有:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL,类型从左至右,性能从高到低。

system:表只有一行。这是最好的可能的连接类型,它只会在查询的表是系统表时出现。
 
const:表最多有一个匹配行,因为MySQL能够通过索引一次性就找到这一行。这发生在使用主键或唯一索引等值查询时。
例如:EXPLAIN SELECT * FROM EMPLOYEES WHERE id = 1;
0
 
eq_ref:在所有的连接类型当中,除了const类型,这个是最优的。它表示前面的表中的每一个记录,都只能在表中匹配一条记录。通常出现在主键或唯一性索引的列上。
例如:EXPLAIN SELECT * FROM DEPARTMENTS D LEFT JOIN EMPLOYEES E ON D.ID = E.ID;
0
 
ref:这种连接类型表示对于前面的每一个表的行组合,索引将会从表中返回所有匹配的行。这种类型出现在使用非唯一性索引,或者使用唯一性索引的查询中只使用了部分列。
例如:EXPLAIN SELECT * FROM EMPLOYEES WHERE department_id = 5;
0
 
EXPLAIN SELECT * FROM DEPARTMENTS D LEFT JOIN DEPARTMENTS_EMPLOYEES E ON D.ID = E.D_ID;
关联表查询,idx_d_e_id是d_id和e_id的联合索引,这里使用到了DEPARTMENTS_EMPLOYEES的左边前缀d_id的部分。
0
 
fulltext:该连接类型使用FULLTEXT索引进行查找。
 
ref_or_null:和ref类型类似,但是MySQL会额外搜索包含null值的行。
 
index_merge:表示使用了索引合并优化方法,在这种方式下,MySQL可以使用多个索引来查找行。
 
unique_subquery:在某些IN查询中使用,比如,WHERE col IN (SELECT unique_key FROM single_table WHERE ...)
 
index_subquery:与unique_subquery类似,但是用在非唯一索引上。
 
range:使用索引来检索给定范围的行,比如出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行。
例如:EXPLAIN SELECT * FROM EMPLOYEES WHERE salary BETWEEN 50000 AND 60000;
0
 
index:表示对索引进行全扫描,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般为使用覆盖索引,二级索引一般比较小,所以这种通常比ALL快一些。
例如:EXPLAIN SELECT * from EMPLOYEES;
0
 
ALL:对全表进行扫描,这种类型通常比较糟糕,应该尽量避免。
例如:EXPLAIN SELECT * FROM DEPARTMENTS;
0
这些类型的性能不一定是绝对的。对于不同的查询,某种类型可能比其他类型更有效。并且,MySQL查询优化器会根据查询的具体情况自动选择最合适的类型。
 

possible_keys:表示可能应用在这张表中的索引。如果为空,表示没有可能的索引。

 
如果 possible_keys 为空,表示在当前查询条件下,没有可能的索引可以用来优化查询。这可能是因为查询条件不适合使用索引,或者在表中没有适用于当前查询的合适索引。可以通过检查 where 子句看是否可以创造一个适当的索引来提高查询性能,然后用 explain 查看效果。
 
如果 possible_keys 不为空,它会列出在当前查询条件下,可能会应用在这张表中的索引的名字。这些索引可以加速查询并提高性能,但不一定会全部使用,实际使用哪些索引还要根据查询的具体情况和数据库的执行计划来决定。
explain 时可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,mysql认为索引对此查询帮助不大,选择了全表查询。 
 

 

key:实际使用的索引。如果为NULL,则没有使用索引。

如果没有使用索引,则该列是 NULL。如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用 force index、ignore index。
 

key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好。

key_len字段显示了MySQL在查询中使用的索引的最大可能长度。这个值以字节为单位,并且反映了索引的“最坏”情况,即索引可以占用的最大空间。
 
key_len字段表示MySQL在查询中所使用的索引的最大可能长度,以字节为单位。它取决于索引中所有部分的最大可能长度。
下面是一些常见数据类型和它们的key_len:

字符串类型(CHAR, VARCHAR):

对于utf8字符集,key_len等于字符数量乘以每个字符的最大字节长度(3字节)。
对于utf8mb4字符集,每个字符最多占用4个字节,所以key_len可能会更大。
 
例如(以utf8字符集举例)
CHAR(10)的key_len是30。
VARCHAR(10),的key_len将为32(10字符 * 3字节/字符 + 2字节);
因为对于VARCHAR,还需要加上用于存储长度信息的1到2个字节。
 

数值类型:对于数值类型,

key_len是固定的,与实际值无关。
例如,
TINYINT的key_len是1字节,
SMALLINT的key_len是2字节,
INT的key_len是4字节,
BIGINT的key_len是8字节。
 

时间类型:

例如,
DATE的key_len是3字节,
TIMESTAMP的key_len是4字节,
DATETIME的key_len是8字节。
 
如果索引是复合索引,包含多个字段,key_len将会是这些字段key_len之和。如果字段允许为NULL,还需要额外1字节来记录是否为NULL。
最后需要注意的是,虽然key_len表示的是索引的最大可能长度,但实际使用的索引长度可能会小于这个值。例如,对于VARCHAR字段,如果实际字符数小于字段定义的字符数,那么实际的索引长度也会小于key_len。
 
InnoDB存储引擎的索引长度限制是767字节(MySQL 5.6以前)或3072字节(MySQL 5.7及以后,需要开启innodb_large_prefix)。这是因为InnoDB存储引擎使用了一个叫做B+Tree的数据结构来存储索引,这个数据结构的特性决定了索引的长度有一定的限制。
 
当一个索引的长度可能超过这个限制时,MySQL会尝试创建一个“前缀索引”,即只取字段的前面一部分作为索引。这对于某些类型的查询可能是足够的,例如,对于LIKE 'abc%'这样的查询,前缀索引可以提供和全值索引相同的性能。
然而,前缀索引也有一些限制和问题。最重要的一点是,前缀索引不能用于ORDER BY或GROUP BY操作,也不能用于部分字符串匹配(例如,LIKE '%abc%')。另外,由于只索引了部分值,前缀索引可能会导致更多的磁盘I/O,因此可能会降低查询性能。
总的来说,如果可能,应尽量避免使用前缀索引,而应该尝试减少索引的长度或者使用更适合的数据类型。如果必须使用前缀索引,应该仔细测试查询性能,并根据需要调整前缀长度。
 

 ref:显示索引的哪一列被使用了,如果可能的话,是一个常数。

 

 rows:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需读取的行数。

 

filtered 列该列是一个百分比的值,rows * filtered/100 可以估算出将要和 explain 中前一个表进行连接的行数(前一个表指 explain 中的id值比当前表id值小的表)。

 

Extra:包含MySQL解决查询的详细信息,如:Using where(表示mysql服务器将在存储引擎检索行后再进行过滤),Using temporary(表示MySQL需要使用临时表来存储结果集),Using filesort(表示MySQL会对结果使用一个外部的索引排序,而不是按照表内的索引顺序来读取)等。

下面是一些常见的值:
Using where:表示MySQL服务器在存储引擎检索完行数据后需要对数据进行过滤,只返回符合WHERE子句条件的记录。例如:
EXPLAIN SELECT * FROM departments WHERE id > 5000;
0
 
Using temporary:表示MySQL需要使用临时表来存储查询结果。这通常发生在排序和分组查询中。例如:
EXPLAIN SELECT DISTINCT name FROM departments 因为name 没索引,创建了张临时表来distinct
0
EXPLAIN SELECT DISTINCT name FROM employees ,name 有索引,所以不需要创建临时表
0
 
Using filesort:表示MySQL需要进行额外的工作来对结果进行排序,而不能直接利用索引的顺序。这通常发生在ORDER BY条件中指定的列没有被索引覆盖的情况下。例如:
EXPLAIN SELECT * FROM departments ORDER BY name
因为name列没有被索引,MySQL会使用一个叫做filesort的算法来进行排序。
0
 
Using index:表示MySQL服务器可以直接使用索引来获取查询的结果,不需要读取表中的实际数据。这通常发生在查询的列都被覆盖在一个索引中的情况下,被称为覆盖索引查询。
覆盖索引定义:mysql执行计划explain结果里的key有使用索引,如果select后面查询的字段都可以从这个索引的树中获取,这种情况一般可以说是用到了覆盖索引,extra里一般都有using index;覆盖索引一般针对的是辅助索引,整个查询结果只通过辅助索引就能拿到结果,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其它字段值
EXPLAIN SELECT name,salary FROM employees
0
 
Select tables optimized away:这表示MySQL优化器在优化查询过程中,发现可以在优化阶段就得到查询的结果,因此实际执行过程中不需要再访问表或索引。这通常发生在对于某些特定类型的查询,例如MIN(),MAX()对于有索引的列,或者COUNT(*)对于MyISAM表。例如:
EXPLAIN SELECT min(id) FROM departments_employees;
0
如果employees表是MyISAM表,因为MyISAM表在表的元数据中直接保存了表的行数,所以MySQL可以直接读取这个值,而不需要通过扫描全表来计算行数,因此显示“Select tables optimized away”。
 
Using index condition:这是MySQL 5.6版本引入的一个新特性,也被称为索引条件推送(Index Condition Pushdown, ICP)。在以前的版本中,MySQL服务器在存储引擎检索完索引后,会把所有符合索引条件的行都取出,然后在服务器层进行WHERE子句中其他条件的过滤。但是在5.6版本以后,其他的WHERE条件可以被推送到存储引擎层,这样只有符合所有条件的行才会被取出。

看到这里如果您觉得我的文章学的不错,请关注我的公众号,微信搜索 “二二零二”,更多内容正在创作者