一、执行计划的概念与作用
(一)生成背景
当面对慢查询语句时,MySQL 查询优化器会基于成本和规则对查询进行优化,并生成执行计划。这个执行计划犹如一张蓝图,详细展示了 MySQL 将如何执行查询,包括多表连接的顺序、对每个表采用的访问方法等。
二)查看方式
通过在 SQL 查询前加上 EXPLAIN 关键字,我们就能获取查询语句的执行计划。例如:EXPLAIN select * from table1。这种方式可以模拟优化器执行 SQL 查询语句,让我们了解 MySQL 是如何处理 SQL 语句的,进而分析查询语句或表结构的性能瓶颈。
(三)重要意义
理解执行计划有助于我们优化查询语句,提高数据库的性能。它能帮助我们发现查询过程中的潜在问题,如不合理的索引使用、低效的表连接方式等,并为优化提供方向。
二、执行计划的输出列详解
(一)id 列 作用:在包含多个 SELECT 关键字的复杂查询中,id 列用于区分每个 SELECT 关键字对应的查询,为其分配唯一的标识符。 取值情况 单 SELECT 关键字查询:如 EXPLAIN SELECT * FROM s1 WHERE order_no = 'a';,id 值为 1。 连接查询:在连接查询中,每个表对应的记录的 id 值相同。例如 EXPLAIN SELECT * FROM s1 INNER JOIN s2;,s1 和 s2 表对应的记录 id 值均为 1。 包含子查询:子查询中的每个 SELECT 关键字也有独立的 id 值。若查询优化器未重写子查询,如 EXPLAIN SELECT * FROM s1 WHERE id IN (SELECT id FROM s2) OR order_no = 'a';,外层查询 id 为 1,子查询 id 为 2。若查询优化器将子查询转换为连接查询,如 EXPLAIN SELECT * FROM s1 WHERE id IN (SELECT id FROM s2 WHERE order_no = 'a');,s1 和 s2 表对应的记录 id 值均为 1。 包含 UNION 子句:UNION 中的每个 SELECT 查询都有对应的 id 值,且会有一个 id 为 NULL 的记录用于表示 UNION 结果集。如 EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;,s1 对应的 id 为 1,s2 对应的 id 为 2,UNION 结果集对应的 id 为 NULL。而对于 UNION ALL,不会出现 id 为 NULL 的记录,如 EXPLAIN SELECT * FROM s1 UNION ALL SELECT * FROM s2;,s1 和 s2 对应的 id 分别为 1 和 2。 (二)select_type 列 作用:用于描述每个 SELECT 关键字代表的小查询在整个大查询中所扮演的角色。 取值及含义 SIMPLE:简单的 select 查询,不使用 union 及子查询,如 EXPLAIN SELECT * FROM s1 WHERE order_no = 'a';。连接查询也属于此类型,如 EXPLAIN SELECT * FROM s1 INNER JOIN s2;。 PRIMARY:最外层的 select 查询。在包含 UNION、UNION ALL 或子查询的大查询中,最左边的查询的 select_type 值为 PRIMARY,如 EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2; 中,SELECT * FROM s1 对应的 select_type 为 PRIMARY。 UNION:UNION 中的第二个或随后的 select 查询,不依赖于外部查询的结果集,如上述 UNION 查询中,SELECT * FROM s2 对应的 select_type 为 UNION。 UNION RESULT:针对 UNION 查询结果集的查询,其 select_type 为 UNION RESULT,如上述 UNION 查询中的第三条记录。 SUBQUERY:子查询中的第一个 select 查询,不依赖于外部查询的结果集,且查询优化器决定采用物化子查询的方案时,该子查询的 select_type 为 SUBQUERY,如 EXPLAIN SELECT * FROM s1 WHERE id IN (SELECT id FROM s2) OR order_no = 'a'; 中子查询的 select_type 为 SUBQUERY。 DEPENDENT UNION:UNION 中的第二个或随后的 select 查询,依赖于外部查询的结果集,如 EXPLAIN SELECT * FROM s1 WHERE id IN (SELECT id FROM s2 WHERE id = 716 UNION SELECT id FROM s1 WHERE id = 718); 中,SELECT id FROM s1 WHERE id = 718 对应的 select_type 为 DEPENDENT UNION。 DEPENDENT SUBQUERY:子查询中的第一个 select 查询,依赖于外部查询的结果集,如上述查询中,SELECT id FROM s2 WHERE id = 716 对应的 select_type 为 DEPENDENT SUBQUERY。 DERIVED:用于 from 子句里有子查询的情况,MySQL 会递归执行这些子查询,并将结果放在临时表里,如 EXPLAIN SELECT * FROM (SELECT id, count (*) as c FROM s1 GROUP BY id) AS derived_s1 where c >1; 中子查询的 select_type 为 DERIVED。 MATERIALIZED:当查询优化器选择将子查询物化后与外层查询进行连接查询时,子查询对应的 select_type 为 MATERIALIZED,如 EXPLAIN SELECT * FROM s1 WHERE order_no IN (SELECT order_no FROM s2); 中子查询的 select_type 为 MATERIALIZED。 UNCACHEABLE SUBQUERY 和 UNCACHEABLE UNION:结果集不能被缓存的子查询,出现极少,在此不做深入讲解。 (三)table 列 作用:表示执行计划中每条记录所对应的表名。 示例说明:在连接查询 EXPLAIN SELECT * FROM s1 INNER JOIN s2; 的执行计划中,有两条记录,分别对应 s1 和 s2 表,其 table 列的值即为相应的表名。 (四)partitions 列 作用:与分区表相关,一般情况下,查询语句执行计划的 partitions 列的值为 NULL。 (五)type 列 作用:表示对单表的访问方法 / 访问类型,是衡量查询性能的重要指标,结果值从最好到最坏依次为 system > const > eq_ref > ref > range > index > ALL。 取值及含义 system:当表中只有一条记录且存储引擎统计数据精确时(如 MyISAM、Memory),对该表的访问方法为 system,如 explain select * from test_myisam;(假设 test_myisam 表符合条件)。 const:根据主键或唯一二级索引列与常数进行等值匹配时,对单表的访问方法为 const,如 EXPLAIN SELECT * FROM s1 WHERE id = 716;。但如果主键或唯一二级索引由多个列构成,需每个列都与常数等值比较时,const 访问方法才有效;对于唯一二级索引列值为 NULL 的情况,不可以使用 const 访问方法。 eq_ref:在连接查询中,被驱动表通过主键或唯一二级索引列等值匹配方式访问(若为联合索引,所有索引列都必须等值比较)时,对该被驱动表的访问方法为 eq_ref,如 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id; 中,s1 表的访问方法为 eq_ref。 ref:通过普通二级索引列与常量进行等值匹配查询时,对该表的访问方法可能为 ref,如 EXPLAIN SELECT * FROM s1 WHERE order_no = 'a';。但如果二级索引列值为 NULL,最多只能使用 ref 访问方法;对于包含多个索引列的二级索引,只要最左边连续索引列是与常数的等值比较,就可能采用 ref 访问方法,如 SELECT * FROM order_exp WHERE insert_time = '2021-03-22 18:28:23'; 和 SELECT * FROM order_exp WHERE insert_time = '2021-03-22 18:28:23' AND order_status = 0;,但如果最左边连续索引列不是全部等值比较,如 SELECT * FROM order_exp WHERE insert_time = '2021-03-22 18:28:23' AND order_status > -1; 则不能使用 ref 访问方法。 ref_or_null:当查询不仅要找出二级索引列值等于常数的记录,还想找出该列值为 NULL 的记录时,可能使用 ref_or_null 访问方法,如 explain SELECT * FROM order_exp_cut WHERE order_no = 'abc' OR order_no IS NULL;(假设 order_exp_cut 表的 order_no 列允许为 NULL)。 index_merge:在某些场景下,可使用索引合并的方式执行查询,如 EXPLAIN SELECT * FROM s1 WHERE order_no = 'a' OR insert_time = '2021-03-22 18:36:47';。 unique_subquery:在包含 IN 子查询的查询语句中,若查询优化器将 IN 子查询转换为 EXISTS 子查询,且子查询可使用主键进行等值匹配时,子查询执行计划的 type 列值为 unique_subquery,如 EXPLAIN SELECT * FROM s1 WHERE id IN (SELECT id FROM s2 where s1.insert_time = s2.insert_time) OR order_no = 'a'; 中子查询的 type 值为 unique_subquery。 index_subquery:与 unique_subquery 类似,但访问子查询中的表时使用的是普通索引,如 EXPLAIN SELECT * FROM s1 WHERE order_no IN (SELECT order_no FROM s2 where s1.insert_time = s2.insert_time) OR order_no = 'a';(与 unique_subquery 示例中 in 子句字段不同)。 range:如果使用索引获取某些范围区间的记录,如在 where 语句中出现 between、<、>、in 等查询时,可能使用 range 访问方法,如 EXPLAIN SELECT * FROM s1 WHERE order_no IN ('a', 'b', 'c'); 和 EXPLAIN SELECT * FROM s1 WHERE order_no > 'a' AND order_no < 'b';。 index:当可以使用索引覆盖,但需要扫描全部索引记录时,表的访问方法为 index,如 EXPLAIN SELECT insert_time FROM s1 WHERE expire_time = '2021-03-22 18:36:47';。 ALL:全表扫描,将遍历全表以找到匹配的行,如 EXPLAIN SELECT * FROM s1;。 (六)possible_keys 与 key 列 作用 possible_keys 列表示在查询语句中,对某个表执行单表查询时可能用到的索引。 key 列表示实际用到的索引,如果为 NULL,则表示没有使用索引。 示例说明:在 EXPLAIN SELECT order_note FROM s1 WHERE insert_time = '2021-03-22 18:36:47'; 的执行计划中,possible_keys 列的值为 u_idx_day_status,idx_insert_time,表示该查询可能使用这两个索引,而 key 列的值为 u_idx_day_status,表示查询优化器最终决定使用该索引执行查询。需要注意的是,在使用 index 访问方法查询时,可能出现 possible_keys 列为空,而 key 列展示实际使用索引的情况,如 EXPLAIN SELECT insert_time FROM s1 WHERE expire_time = '2021-03-22 18:36:47';。此外,possible_keys 列中的索引并非越多越好,过多的索引会增加查询优化器计算成本,应尽量删除无用索引。 (七)key_len 列 作用:表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度。其计算方式考虑了索引列的数据类型、是否可为 NULL 以及字符集等因素,可用于判断复合索引的使用情况。 计算方式及示例 对于使用固定长度类型的索引列,其实际占用存储空间的最大长度即为该固定值。 对于指定字符集的变长类型索引列,如 VARCHAR (100) 且字符集为 utf8,实际占用最大存储空间为 100 x 3 = 300 字节。若该索引列可存储 NULL 值,则 key_len 比不可存储 NULL 值时多 1 字节;对于变长字段,会有 2 个字节空间存储实际长度。例如,EXPLAIN SELECT * FROM s1 WHERE id = 718; 中,id 列类型为 bigint 且不可存储 NULL 值,key_len 为 8;EXPLAIN SELECT * FROM s1 WHERE order_no = 'a'; 中,order_no 列类型为 VARCHAR (50),其 key_len 为 50 x 3 + 2 = 152(假设字符集为 utf8)。 (八)ref 列 作用:当使用索引列等值匹配条件执行查询时(访问方法为 const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery 之一),ref 列展示与索引列进行等值匹配的对象信息,可能是常数、列或函数等。 示例说明:在 EXPLAIN SELECT * FROM s1 WHERE order_no = 'a'; 中,ref 列的值为 const,表明与 order_no 列等值匹配的对象是常数;在 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id; 中,对被驱动表 s2 的访问方法为 eq_ref,ref 列的值为 mysqladv.s2.id,说明与 s2 表的 id 列作等值匹配的对象是 mysqladv.s2.id 列;在 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.order_no = UPPER (s1.order_no); 中,ref 列的值为 func,表示与索引列进行等值匹配的对象是一个函数。 (九)rows 列 作用:如果查询优化器决定使用全表扫描,rows 列代表预计需要扫描的行数;若使用索引执行查询,rows 列代表预计扫描的索引记录行数。 示例说明:在 EXPLAIN SELECT * FROM s1 WHERE order_no > 'z'; 和 EXPLAIN SELECT * FROM s1 WHERE order_no > 'a'; 中,rows 列的值分别为 1 和 10573,这是查询优化器根据分析使用 idx_order_no 索引查询的成本后得出的预计扫描行数。 (十)filtered 列 作用:查询优化器预测有多少条记录满足其余的搜索条件。对于单表查询意义不大,在连接查询中,驱动表对应的 filtered 值更受关注,可用于估算连接查询中对被驱动表的查询次数。 示例说明:在 EXPLAIN SELECT * FROM s1 WHERE id > 5890 AND order_note = 'a'; 中,从执行计划可看出使用 PRIMARY 索引执行查询,满足 id > 5890 的记录有 5286 条,filtered 列的值代表在这 5286 条记录中满足 order_note = 'a' 条件的记录百分比。在 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.order_no = s2.order_no WHERE s1.order_note > ' 你好,李焕英 '; 中,驱动表 s1 的 rows 列为 10573,filtered 列为 33.33,意味着驱动表的扇出值为 10573 x 33.33% = 3524.3,即大约要对被驱动表执行 3524 次查询。 (十一)Extra 列 作用:用于说明一些额外信息,帮助我们更准确地理解 MySQL 如何执行查询语句。常见的额外信息有以下几种: No tables used:当查询语句没有 FROM 子句时提示。 Impossible WHERE:查询语句的 WHERE 子句永远为 FALSE 时提示。 No matching min/max row:查询列表处有 MIN 或 MAX 聚集函数,但无符合 WHERE 子句搜索条件的记录时提示。 Using index:查询列表及搜索条件只包含属于某个索引的列,即可以使用索引覆盖时提示,如 EXPLAIN SELECT expire_time FROM s1 WHERE insert_time = '2021-03-22 18:36:47';。 Using index condition:有些搜索条件虽出现索引列,但不能完全使用索引,如 SELECT * FROM s1 WHERE order_no > 'z' AND order_no LIKE '% a';,改进后的执行方式涉及索引条件下推,会在 Extra 列显示此提示。 Using where:使用全表扫描且 WHERE 子句有针对该表的搜索条件时,或使用索引访问且 WHERE 子句有索引列之外的其他搜索条件时提示,但 Using where 仅表示使用 where 子句对记录进行过滤,与是否全表扫描或读取索引文件无关。 Using join buffer (Block Nested Loop):在连接查询中,被驱动表不能有效利用索引加快访问速度时,MySQL 会为其分配 join buffer 内存块,此时在 Extra 列显示此提示,如 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.order_note = s2.order_note; 中对 s1 表的执行计划 Extra 列会显示此提示。 Not exists:使用左(外)连接,WHERE 子句包含要求被驱动表的某个不允许存储 NULL 值的列等于 NULL 的搜索条件时提示,如 EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.order_no = s2.order_no WHERE s2.id IS