这是《PostgreSQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 执行计划是 PostgreSQL 性能优化的地图。优化 SQL 前,先能读懂每个节点的访问方式、行数估算、循环次数、排序和内存使用。
7.1 EXPLAIN
EXPLAIN
SELECT user_id, count(*), sum(amount)
FROM orders
WHERE status = 'PAID'
GROUP BY user_id;
EXPLAIN 只显示计划,不执行。查看真实执行指标使用:
EXPLAIN ANALYZE
SELECT ...
查看缓冲:
EXPLAIN (ANALYZE, BUFFERS)
SELECT ...
ANALYZE 会真正执行 DML 或耗时 SELECT,生产慎用。
7.2 常见扫描节点
| 节点 | 说明 |
|---|---|
| Seq Scan | 全表顺序扫描 |
| Index Scan | 索引扫描并回表 |
| Index Only Scan | 只读索引,需可见性映射支持 |
| Bitmap Index Scan | 生成位图 |
| Bitmap Heap Scan | 按位图回表 |
| Function Scan | 函数结果扫描 |
| Foreign Scan | 外部表扫描 |
Seq Scan 不一定是坏事。小表、大比例返回、没有合适索引时,它可能成本最低。
7.3 连接节点
EXPLAIN ANALYZE
SELECT u.name, o.amount
FROM users u
JOIN orders o ON o.user_id = u.id;
| 节点 | 适合 |
|---|---|
| Nested Loop | 外层小、内层能走索引 |
| Hash Join | 大表等值连接 |
| Merge Join | 输入已按连接键排序 |
如果 Nested Loop 内层是 Seq Scan 且 loops 很大,通常是缺索引或统计信息错误。
7.4 聚合与排序
常见节点:
| 节点 | 说明 |
|---|---|
| Aggregate | 普通聚合 |
| GroupAggregate | 按序分组 |
| HashAggregate | 哈希分组 |
| Sort | 显式排序 |
| Incremental Sort | 部分有序输入 |
| Unique | 去重 |
Sort 内存超出 work_mem 会溢写临时文件:
Sort Method: external merge Disk: 123456kB
7.5 行数估算
rows=1000
actual time=... rows=1000000 loops=1
估算与实际差异过大时,优化器可能选错计划。
处理:
ANALYZE orders;
SELECT relname, n_live_tup, last_analyze
FROM pg_stat_user_tables;
再检查是否有统计信息、WHERE 表达式是否可估算、是否跨列相关、是否需要扩展统计、是否参数被折叠成常量。
7.6 关键指标
cost=startup..total
rows=估算行数
width=平均行宽
actual time=启动..完成
rows=实际行数
loops=执行次数
Shared Hit / Read = 缓冲命中与磁盘读取
重点看每行耗时和外层循环放大,而不是只看第一行 cost。
7.7 执行计划开关
SET enable_seqscan = off;
这类开关用于验证索引是否可用,不应作为生产“优化配置”。更好的方式是调整统计信息、索引、SQL 或代价参数。
7.8 计划管理
PostgreSQL 支持扩展的计划管理方案,如 pg_hint_plan 等工具,具体可用性取决于版本和发行版。生产引入前要确认维护成本。
原生可先做:
- 固化 SQL;
- 更新统计;
- 建合适索引;
- 避免隐式转换;
- 减少返回数据;
- 记录基线计划。
7.9 慢计划阅读流程
1. 从最内层扫描看起
2. 检查过滤条件和索引匹配
3. 比较估算 rows 与 actual rows
4. 检查 loops
5. 检查 Sort / Hash 内存
6. 检查 Rows Removed
7. 优化后对比 buffers 和时间
本章小结
执行计划回答三个问题:扫了多少数据、用了什么算法、估算是否可靠。读懂 ANALYZE 和 BUFFERS 输出,是 PostgreSQL 优化的基本功。
思考题
- Index Scan 和 Index Only Scan 差异是什么?
- loops 很大说明什么?
- Sort 溢写磁盘如何处理?
- 估算行数偏差过大会带来什么?
- 为什么
enable_seqscan = off不适合生产?