SQL 执行计划(SQL Execution Plan)是数据库查询优化器(Query Optimizer)为 SQL 语句生成的具体执行方案,它展示了数据库将如何(或已经如何)执行一条 SQL 查询。通过分析执行计划,开发者可以了解查询的性能瓶颈,从而进行优化。
一、执行计划的作用
- 性能诊断:识别慢查询的原因(如全表扫描、索引失效、连接方式不佳等)。
- 优化指导:指导开发者调整 SQL 写法(如添加索引、修改连接条件、重写子查询等)。
- 理解执行逻辑:明确数据库执行 SQL 时的步骤(如连接顺序、数据过滤顺序、聚合方式等)。
二、获取执行计划的方法
不同数据库获取执行计划的方式略有差异,以下是主流数据库的方法:
1. MySQL
- 查看预估执行计划(未实际执行 SQL):
EXPLAIN SELECT * FROM users WHERE age > 20; - 查看实际执行计划(需开启 profiling):
SET profiling = 1; -- 开启 profiling SELECT * FROM users WHERE age > 20; -- 执行 SQL SHOW PROFILE FOR QUERY 1; -- 查看执行详情(1 是查询 ID)
2. Oracle
- 查看预估执行计划:
EXPLAIN PLAN FOR SELECT * FROM users WHERE age > 20; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); -- 展示执行计划 - 查看实际执行计划(需开启追踪):
ALTER SESSION SET SQL_TRACE = TRUE; -- 开启追踪 SELECT * FROM users WHERE age > 20; ALTER SESSION SET SQL_TRACE = FALSE; -- 关闭追踪 -- 然后查看追踪文件(需通过 TKPROF 工具分析)
3. PostgreSQL
- 查看预估执行计划:
EXPLAIN SELECT * FROM users WHERE age > 20; - 查看实际执行计划(执行 SQL 并展示计划):
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 20;
4. SQL Server
- 查看预估执行计划(SSMS 图形化界面或 T-SQL):
SET SHOWPLAN_TEXT ON; -- 文本格式 SELECT * FROM users WHERE age > 20; SET SHOWPLAN_TEXT OFF; - 查看实际执行计划:
SET STATISTICS PROFILE ON; -- 文本格式 SELECT * FROM users WHERE age > 20; SET STATISTICS PROFILE OFF;
三、执行计划核心组件解析
以 MySQL 的 EXPLAIN 输出为例,核心字段含义如下(其他数据库类似):
| 字段 | 含义 |
|---|---|
id | 查询的执行顺序(相同 ID 表示同一层级,ID 越大优先级越高,子查询 ID 通常更大)。 |
select_type | 查询类型(如 SIMPLE 简单查询、SUBQUERY 子查询、DERIVED 派生表、JOIN 连接查询)。 |
table | 当前执行步骤操作的表(可能是表名、别名,或派生表的标识如 <derived2>)。 |
type | 访问类型(核心性能指标),优先级从高到低:system > const > eq_ref > ref > range > index > ALL。 |
possible_keys | 可能使用的索引(优化器认为潜在可用的索引)。 |
key | 实际使用的索引(NULL 表示未使用索引)。 |
key_len | 使用的索引长度(越长表示索引利用率越高,如联合索引使用的字段数越多)。 |
ref | 索引匹配的条件(如 const 表示常量匹配,users.age 表示关联其他表的字段)。 |
rows | 预估扫描的行数(值越小越好,说明过滤效果好)。 |
Extra | 额外信息(关键性能提示),如 Using index(覆盖索引)、Using filesort(文件排序,需优化)、Using temporary(临时表,需优化)等。 |
关键字段详解
1. type(访问类型)
system:表中只有一行数据(如SELECT * FROM dual),性能最好。const:通过主键或唯一索引查询,匹配一行数据(如SELECT * FROM users WHERE id = 1)。eq_ref:多表连接时,被连接表通过主键 / 唯一索引匹配,每行只匹配一次(如JOIN orders ON users.id = orders.user_id,orders.user_id是主键)。ref:通过非唯一索引匹配(如SELECT * FROM users WHERE name = '张三',name是普通索引)。range:索引范围扫描(如WHERE age BETWEEN 20 AND 30、WHERE id > 100)。index:全索引扫描(扫描整个索引树,比ALL快,因为索引数据量通常比表数据小)。ALL:全表扫描(性能最差,需避免,通常是未建索引或索引失效导致)。
2. Extra(额外信息)
Using index:使用覆盖索引(查询的字段全部在索引中,无需回表查询数据行),性能优秀。Using filesort:无法利用索引排序,需在内存或磁盘中进行文件排序(耗时高,需优化,如给排序字段建索引)。Using temporary:使用临时表存储中间结果(如GROUP BY或UNION未命中索引时),性能差,需优化。Using join buffer:多表连接时,使用连接缓冲区存储中间结果(通常是连接字段未建索引,需优化)。Impossible WHERE:WHERE条件恒为假(如WHERE 1 = 0),无需扫描数据。
四、执行计划分析示例
示例 1:全表扫描(需优化)
EXPLAIN SELECT * FROM users WHERE age > 20;
输出关键信息:
type: ALL(全表扫描)key: NULL(未使用索引)rows: 10000(预估扫描 1 万行)
优化方案:给 age 字段建索引:
CREATE INDEX idx_users_age ON users(age);
示例 2:索引失效(需优化)
-- 索引 idx_users_name 存在,但查询条件使用函数
EXPLAIN SELECT * FROM users WHERE UPPER(name) = 'ZHANGSAN';
输出关键信息:
type: ALL(全表扫描)key: NULL(索引失效)
优化方案:避免在索引字段上使用函数,或改⽤函数索引(MySQL 8.0+):
-- 方案 1:修改 SQL(不推荐,需业务允许)
SELECT * FROM users WHERE name = 'zhangsan';
-- 方案 2:创建函数索引(推荐)
CREATE INDEX idx_users_upper_name ON users((UPPER(name)));
示例 3:覆盖索引(性能优秀)
-- 索引 idx_users_age_name 包含 age 和 name 字段
EXPLAIN SELECT name FROM users WHERE age > 20;
输出关键信息:
type: range(索引范围扫描)key: idx_users_age_name(使用索引)Extra: Using index(覆盖索引,无需回表)
结论:无需优化,性能已达最优。
五、执行计划优化常见场景
- 全表扫描(
type: ALL):给查询条件的字段建索引。 - 索引失效:
- 避免在索引字段上使用函数、运算(如
WHERE age + 1 = 21改为WHERE age = 20)。 - 避免使用
!=、<>、IS NOT NULL(可能导致索引失效,可改⽤BETWEEN或IN)。 - 字符串查询需匹配字段类型(如
WHERE id = '1'改为WHERE id = 1,避免隐式类型转换)。
- 避免在索引字段上使用函数、运算(如
- 文件排序(
Using filesort):给排序字段建索引(若有WHERE条件,可建联合索引,如WHERE age > 20 ORDER BY name建索引idx_age_name)。 - 临时表(
Using temporary):GROUP BY字段建索引。UNION改UNION ALL(若允许重复结果,避免去重操作)。
- 多表连接优化:
- 小表驱动大表(如
SELECT * FROM small_table t1 JOIN big_table t2 ON t1.id = t2.t1_id)。 - 连接字段建索引(避免
Using join buffer)。
- 小表驱动大表(如
六、总结
SQL 执行计划是优化查询性能的核心工具,通过分析 type、key、Extra 等关键字段,可以快速定位性能瓶颈。优化的核心思路是:尽量让数据库使用高效的索引访问方式(如 ref、range),避免全表扫描、文件排序、临时表等低效操作。
在实际开发中,应养成 “先看执行计划,再优化 SQL” 的习惯,尤其是在处理大数据量查询时,执行计划的分析能显著提升系统性能。

被折叠的 条评论
为什么被折叠?



