Skip to content

EXPLAIN 分析:理解查询计划 ​

EXPLAIN 基础 ​

sql
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';

输出(PostgreSQL):

text
Seq Scan on users  (cost=0.00..25.00 rows=1 width=100)
  Filter: (email = 'alice@example.com'::text)
  • Seq Scan:顺序扫描(全表扫描)
  • cost:估算成本(启动成本..总成本)
  • rows:预计返回行数
  • width:平均行宽度(字节)

EXPLAIN ANALYZE ​

sql
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@example.com';

输出:

text
Seq Scan on users  (cost=0.00..25.00 rows=1 width=100) (actual time=0.012..0.045 rows=1 loops=1)
  Filter: (email = 'alice@example.com'::text)
Planning Time: 0.123 ms
Execution Time: 0.089 ms
  • actual time:实际执行时间
  • rows:实际返回行数
  • loops:执行次数

常见执行计划 ​

Index Scan:索引扫描 ​

sql
CREATE INDEX idx_email ON users(email);
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@example.com';
text
Index Scan using idx_email on users  (cost=0.29..8.30 rows=1 width=100)
  Index Cond: (email = 'alice@example.com'::text)

Index Only Scan:覆盖索引 ​

sql
CREATE INDEX idx_email_name ON users(email, name);
EXPLAIN SELECT email, name FROM users WHERE email = 'alice@example.com';
text
Index Only Scan using idx_email_name on users

不需要访问表,只读索引。

Nested Loop:嵌套循环 ​

sql
EXPLAIN SELECT * FROM orders o JOIN users u ON o.user_id = u.id;
text
Nested Loop
  -> Seq Scan on orders
  -> Index Scan using users_pkey on users

Hash Join:哈希连接 ​

text
Hash Join
  Hash Cond: (o.user_id = u.id)
  -> Seq Scan on orders
  -> Hash
        -> Seq Scan on users

MySQL EXPLAIN ​

sql
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com'\G

输出:

text
           id: 1
  select_type: SIMPLE
        table: users
         type: ref
possible_keys: idx_email
          key: idx_email
      key_len: 767
          ref: const
         rows: 1
        Extra: Using index condition

关键字段:

  • type:ALL(全表)< index < range < ref < eq_ref < const
  • key:实际使用的索引
  • rows:扫描行数
  • Extra:额外信息

识别性能问题 ​

全表扫描 ​

text
Seq Scan on large_table  (cost=0.00..10000.00 rows=1000000)

解决:创建索引

估算偏差 ​

text
estimated rows=1000, actual rows=100000

解决:ANALYZE 更新统计信息

不使用索引 ​

sql
-- 函数包裹列
WHERE YEAR(order_date) = 2026  -- 不使用索引

-- 改写
WHERE order_date BETWEEN '2026-01-01' AND '2026-12-31'  -- 使用索引

练习 ​

  1. 使用 EXPLAIN 分析简单查询
  2. 对比有无索引的执行计划
  3. 使用 EXPLAIN ANALYZE 查看实际时间
  4. 分析 JOIN 查询的执行计划
  5. 识别未使用索引的查询
  6. 分析复杂查询的性能瓶颈
  7. 使用 ANALYZE 更新统计信息
  8. 优化慢查询

完成练习后,打开本章答案页核对。

下一步 ​

下一节学习查询优化:常见技巧和最佳实践。

回到第六章概览 · 返回SQL 学习路线

用心记录每一步成长