MySQL 执行计划(Execution Plan)是 MySQL 在执行 SQL 查询时,所采取的具体执行策略。它描述了查询如何从数据库中获取数据,执行的步骤顺序以及使用的索引等信息。通过执行计划,我们可以直观地看到查询语句的执行路径,从而判断是否可以优化。
查看执行计划有几种方式:
EXPLAIN 语句
使用 EXPLAIN
可以查看单个查询的执行计划。例如:
EXPLAIN SELECT * FROM employees WHERE department_id = 5;
输出类似如下:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 1000 | Using where |
EXPLAIN ANALYZE
EXPLAIN ANALYZE
会执行查询并返回执行计划,同时还会给出实际执行时间与行数等信息,帮助更精确地理解查询的性能。例如:
EXPLAIN ANALYZE SELECT * FROM employees WHERE department_id = 5;
这个命令不仅会显示执行计划,还会输出执行过程中的时间和实际扫描的行数。
SHOW PROFILE
SHOW PROFILE
是另一种查看查询执行性能的方式,提供了更多关于查询资源使用的信息(需要 MySQL 5.0 及以上版本)。
SET profiling = 1;
SELECT * FROM employees WHERE department_id = 5;
SHOW PROFILE FOR QUERY 1;
解释:id
表示查询执行的顺序,对于单一查询来说,id
只有一个值。而在复杂的查询(如多表连接)中,id
用来表示执行的层次,值越小,执行的优先级越高。
示例:
EXPLAIN SELECT e.name, d.name FROM employees e JOIN departments d ON e.department_id = d.id WHERE e.salary > 5000;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 1000 | Using where |
1 | SIMPLE | departments | eq_ref | PRIMARY | PRIMARY | 4 | employees.department_id | 1 |
分析:id
代表查询的执行顺序。在这种情况下,employees
和 departments
表的查询是平行的(id
值相同),但由于 employees
表使用 ref
类型连接,departments
使用 eq_ref
连接。
解释:select_type
表示查询的类型,主要有以下几种:
SIMPLE
:简单查询,未使用子查询。PRIMARY
:外部查询(如果存在子查询)。UNION
:UNION 查询中的第二个及后续查询。SUBQUERY
:子查询。示例:
EXPLAIN SELECT e.name FROM employees e WHERE e.department_id IN (SELECT id FROM departments WHERE name = 'Engineering');
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | employees | ref | department_id | idx_dept | 4 | subquery | 1000 | Using where |
2 | SUBQUERY | departments | ALL | NULL | NULL | NULL | NULL | 5 |
分析:select_type
指明查询类型,其中 PRIMARY
表示主查询,SUBQUERY
表示内层查询。这里内层查询是 SELECT id FROM departments WHERE name = 'Engineering'
,外层查询则基于该结果进行过滤。
解释:table
显示查询涉及的表名。如果是多表查询,table
会列出每个参与查询的表。
示例:
EXPLAIN SELECT e.name, d.name FROM employees e JOIN departments d ON e.department_id = d.id;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 1000 | |
1 | SIMPLE | departments | eq_ref | PRIMARY | PRIMARY | 4 | employees.department_id | 1 |
分析:table
显示了查询中涉及的表。在这里,employees
和 departments
是查询的两个表。
解释:type
描述了 MySQL 如何查找表中的行,表示连接的方式。常见的连接类型按效率从高到低排序:
const
:常量查找,效率最高。eq_ref
:精确匹配查找。ref
:非唯一匹配查找。range
:范围扫描,使用索引。index
:全索引扫描。ALL
:全表扫描,效率最低。示例:
EXPLAIN SELECT * FROM employees WHERE department_id = 5;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 1000 | Using where |
分析:type
为 ref
,表示 MySQL 使用索引(idx_dept
)通过 department_id
列查找匹配的行。
解释:key
显示查询实际使用的索引,如果没有使用索引,则显示 NULL
。
示例:
EXPLAIN SELECT * FROM employees WHERE department_id = 5 AND salary > 5000;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 500 | Using where |
分析:key
为 idx_dept
,表示查询使用了 department_id
列的索引。
解释:key_len
表示查询使用的索引的长度。数值越小,表示索引覆盖的字段越少,查询效率可能越低。
示例:
EXPLAIN SELECT * FROM employees WHERE department_id = 5 AND salary > 5000;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 500 | Using where |
分析:key_len
为 4,表示 MySQL 使用了长度为 4 字节的索引(可能是 department_id
的索引)。
解释:ref
显示与当前行匹配的列或常数,通常是连接条件或查询过滤条件。
示例:
EXPLAIN SELECT * FROM employees WHERE department_id = 5;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 500 | Using where |
分析:ref
为 const
,表示查询中 department_id
的值为常量 5
。
解释:rows
表示 MySQL 估算的要扫描的行数。这个值越小,查询性能通常越好。
示例:
EXPLAIN SELECT * FROM employees WHERE department_id = 5;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 500 | Using where |
分析:rows
显示 MySQL 估算会扫描 500 行数据。
解释:Extra
字段显示关于查询执行的额外信息,包括使用了哪些优化技巧和操作。常见的 Extra
字段值包括:
Using where
表示查询在数据检索后使用了 WHERE
子句进行进一步的过滤操作。这意味着,在表中获取到的数据行还需经过额外的计算来满足查询条件。
示例:
EXPLAIN SELECT * FROM employees WHERE department_id = 5 AND salary > 5000;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 1000 | Using where |
分析:
Using where
表示 MySQL 在扫描到与 department_id = 5
匹配的行之后,还需要通过 WHERE salary > 5000
进一步过滤数据。这通常意味着数据表上可能没有为两个条件列(department_id
和 salary
)创建复合索引。优化建议:
department_id
和 salary
创建复合索引,从而减少查询时的 WHERE
过滤操作。Using index
表示查询操作使用了 覆盖索引(Covering Index),即查询的所有数据都可以从索引中直接获取,而无需回表访问表数据。索引覆盖通常会提高查询效率,因为它避免了从数据表中加载额外的行数据。
示例:
EXPLAIN SELECT department_id, salary FROM employees WHERE department_id = 5;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 1000 | Using index |
分析:
Using index
表示查询的所有数据(department_id
和 salary
)可以直接从索引 idx_dept
中获取,避免了额外的回表查询。这通常会显著提高查询性能,尤其是在查询列较少时。优化建议:
Using filesort
表示 MySQL 在执行查询时需要额外的排序操作。即使查询中明确包含了 ORDER BY
子句,MySQL 也可能需要额外的步骤来对结果进行排序。文件排序通常会比索引排序慢,因为它需要将数据加载到内存中或临时文件中进行排序。
示例:
EXPLAIN SELECT * FROM employees ORDER BY salary;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 1000 | Using filesort |
分析:
Using filesort
表示 MySQL 在执行 ORDER BY salary
时没有使用索引,而是需要对结果集进行文件排序。这可能是因为 salary
上没有索引,或者查询中有其他导致无法使用索引的情况。优化建议:
salary
排序,可以考虑为 salary
列创建索引,尤其是在大数据量的表中,使用索引可以大大提高排序效率。Using temporary
表示 MySQL 在执行查询时使用了临时表。这通常发生在以下几种情况:
GROUP BY
或 DISTINCT
操作。JOIN
操作。临时表通常会存储在磁盘中,这会增加 I/O 操作,并降低查询性能。
示例:
EXPLAIN SELECT department_id, COUNT(*) FROM employees GROUP BY department_id;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 1000 | Using temporary, Using filesort |
分析:
Using temporary
和 Using filesort
表示 MySQL 在执行 GROUP BY
时需要创建临时表,并且可能还需要进行文件排序。这通常是因为查询没有使用索引,或者 GROUP BY
聚合操作导致了临时表的创建。优化建议:
department_id
创建索引,优化 GROUP BY
操作,减少临时表的使用。GROUP BY
和 ORDER BY
组合,或者考虑改写查询,减少内存和磁盘上的临时表操作。Using join buffer
表示在执行 JOIN
操作时,MySQL 使用了连接缓冲区。通常情况下,MySQL 会将连接的两个表缓存到内存中,尤其是在执行 JOIN
操作时。如果 MySQL 使用了连接缓冲区,可能意味着没有适当的索引来加速连接操作。
示例:
EXPLAIN SELECT e.name, d.name FROM employees e JOIN departments d ON e.department_id = d.id;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 1000 | Using join buffer |
1 | SIMPLE | departments | eq_ref | PRIMARY | PRIMARY | 4 | employees.department_id | 1 |
分析:
Using join buffer
表示 MySQL 在执行 JOIN
时使用了额外的内存缓冲区。这通常表明 department_id
上缺少适当的索引,导致 MySQL 必须在内存中缓存较大的数据集进行连接操作。优化建议:
JOIN
操作中涉及的列(如 department_id
)上有合适的索引。如果没有,考虑为这些列添加索引,以减少连接操作中的内存使用和执行时间。Using index condition
表示 MySQL 在查询过程中能够利用索引条件推导来过滤数据。它会在扫描索引时直接应用查询条件,而不需要访问实际的数据表。这通常提高了查询效率。
示例:
EXPLAIN SELECT * FROM employees WHERE department_id = 5 AND salary > 5000;
执行计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | department_id | idx_dept | 4 | const | 500 | Using index condition |
分析:
Using index condition
表示 MySQL 在扫描索引时就应用了 salary > 5000
的条件,而无需回表查询数据。这是索引条件推导的效果,能够提高查询效率。优化建议:
通过分析执行计划,常见的优化方法包括:
如果查询频繁使用某些列进行条件过滤或排序,考虑为这些列添加索引。例如,如果 department_id
是常用的过滤条件,应该为其创建索引。
CREATE INDEX idx_dept ON employees(department_id);
ALL
类型通常意味着全表扫描,效率较低。此时,应该检查是否有合适的索引可以加速查询。
如果查询只涉及索引中的列,MySQL 可以直接从索引中获取数据,避免回表查询。这种情况下,使用覆盖索引可以显著提高查询效率。
某些查询在执行时可以通过改写 SQL 语句来优化。例如,避免使用 SELECT *
,而应该只选择必要的列。
如果子查询返回大量数据,尝试将其改写为连接查询,或者使用 EXISTS
代替 IN
,从而减少不必要的数据扫描。
type
显示 ALL
,查询变得很慢?ALL
表示 MySQL 执行全表扫描。可能是因为没有合适的索引。检查是否为查询条件列创建了索引,或者是否需要优化查询。
rows
显示很大,但查询仍然很慢?这可能意味着查询扫描了大量无效的数据,或者 MySQL 选择了不合适的索引。检查索引是否匹配查询条件,或者尝试使用 EXPLAIN ANALYZE
查找更多细节。
EXPLAIN
检查是否有全表扫描。JOIN
替代 IN
或 EXISTS
,减少查询的复杂度。MySQL 执行计划是优化查询性能的关键工具,帮助我们了解查询的执行路径。通过分析执行计划中的各个参数,如 type
、key
、rows
等,我们可以针对性地优化查询。常见的优化方法包括创建索引、避免全表扫描、使用覆盖索引等。掌握执行计划的分析与优化技巧,能大大提高系统的性能和响应速度。