SQL 执行计划

SQL 执行计划(Execution Plan)是数据库优化器生成的、描述 SQL 语句如何被执行的“路线图”。它告诉你:数据库会用什么顺序访问表、走不走索引、怎么 JOIN、是否排序/分组、预计成本是多少,是性能调优的核心依据。

下面按「概念 → 怎么看 → 关键指标 → 常见算子 → 实战解读 → 调优思路」系统讲一遍。


一、什么是执行计划

执行计划本质是一棵操作符树

  • 每个节点是一个“执行算子”(Table Scan、Index Seek、Nested Loop Join 等)
  • 数据从叶子节点向上流动,经过过滤、连接、排序等操作
  • 优化器会在多个候选计划中,选一个“代价最低”的

作用:

  • 判断 SQL 是否走索引
  • 发现不合理的 JOIN 顺序
  • 定位慢的根本原因(全表扫描、大排序、临时表等)

二、如何查看执行计划

1. MySQL / MariaDB

-- 实际执行 + 成本信息(推荐)
EXPLAIN ANALYZE SELECT ...;

-- 仅预估计划
EXPLAIN SELECT ...;

-- 格式化输出(8.0+)
EXPLAIN FORMAT=JSON SELECT ...;

2. PostgreSQL

EXPLAIN SELECT ...;                 -- 预估
EXPLAIN ANALYZE SELECT ...;         -- 实际执行(会真的跑)
EXPLAIN (ANALYZE, BUFFERS) SELECT ...;

3. Oracle

EXPLAIN PLAN FOR SELECT ...;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

4. SQL Server

SET STATISTICS PROFILE ON;
-- 执行 SQL
SET STATISTICS PROFILE OFF;
-- 或用 SSMS:Ctrl + L 显示预估计划

三、执行计划中的关键字段(以 MySQL 为例)

字段 含义
id 执行顺序,id 越大越先执行;相同 id 从上到下
select_type 查询类型(SIMPLE、PRIMARY、SUBQUERY 等)
table 当前操作的表
type 访问类型(最重要之一)
possible_keys 可能使用的索引
key 实际使用的索引
rows 预估扫描行数
filtered 条件过滤后的行占比
Extra 额外信息(Using index、Using temporary、Using filesort 等)

四、type(访问类型)优劣排序(MySQL)

从好到坏大致是:

system
const
eq_ref
ref
range
index
ALL
  • const / eq_ref:主键 / 唯一索引等值查询,极快
  • ref:普通索引等值查询
  • range:索引范围扫描(BETWEEN、IN、>、<)
  • index:全索引扫描(比 ALL 好一点)
  • ALL:全表扫描 ❌(通常要优化)

五、常见执行算子 & Extra 信息

1. 扫描类

  • ALL全表扫描,数据量大时必须警惕
  • index:扫描整个索引树,通常出现在覆盖索引但无过滤条件
  • range:范围扫描,常见于 WHERE id > 100

2. JOIN 算法

  • Nested Loop Join
    • 驱动表一行一行去匹配被驱动表
    • 被驱动表必须有高效索引
  • Hash Join(MySQL 8.0+、PG、Oracle)
    • 适合大表 JOIN,内存中建哈希表
  • Merge Join
    • 两个表都按 JOIN 列排序后合并

3. Extra 中的“危险信号”

Extra 含义 建议
Using filesort 外部排序(无可用索引) 加 ORDER BY 索引
Using temporary 使用临时表 优化 GROUP BY / DISTINCT
Using join buffer JOIN 没用到索引 给被驱动表加索引
Using where 在存储引擎层之后过滤 正常,但可结合索引优化

六、一个完整解读示例

EXPLAIN ANALYZE
SELECT o.order_id, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.create_time >= '2025-01-01'
ORDER BY o.create_time;

理想情况:

  • orders.type = range,走 idx_create_time
  • customers.type = eq_ref,走主键 id
  • Extra: Using index(覆盖索引)
  • Using filesort、无 Using temporary

问题情况:

  • orders.type = ALL → 全表扫描
  • customers.type = ALL → JOIN 无索引
  • Using filesortcreate_time 未参与索引顺序

七、基于执行计划的调优思路

1. 减少扫描行数

  • 确保 WHERE 条件能用索引
  • 避免对索引列做函数操作:
    -- 不好
    WHERE DATE(create_time) = '2025-01-01'
    -- 好
    WHERE create_time >= '2025-01-01' AND create_time < '2026-01-01'
    

2. 合理设计联合索引

遵循 最左前缀原则

INDEX (a, b, c)
-- 能用到:a | a,b | a,b,c
-- 不能用:b | b,c

等值条件字段放前面,范围条件放后面

3. 控制 JOIN 顺序

  • 小表驱动大表
  • 确保被驱动表的 JOIN 字段有索引

4. 避免隐式转换

-- customer_id 是 VARCHAR
WHERE customer_id = 123; -- 会导致索引失效

5. 利用覆盖索引

-- 只查索引列,避免回表
SELECT id, status FROM orders WHERE status = 'PAID';
-- INDEX(status, id)

八、不同数据库的差异(简要)

数据库 特点
MySQL 优化器相对简单,索引设计影响极大
PostgreSQL 支持 Hash Join、并行执行,统计信息更重要
Oracle CBO 非常成熟,Hint 丰富
SQL Server 图形化执行计划强,关注“实际行数 vs 预估行数”

九、一个实用口诀

先看 type,再看 key,rows 多少心里知;
filesort 和 temp,Extra 里找问题;
JOIN 看驱动表,被驱要有索引;
范围放最后,等值放前头。

上一篇
下一篇