PostgreSQL EXPLAIN 是什么
PostgreSQL 的 EXPLAIN 输出一棵计划树,每个节点带上启动成本与总成本估算:cost=启动..总。cost 是统计模型算出的抽象单位,与页读取代价参数相关,适合同一实例上的计划横向比较,不能当作毫秒读。缩进层级表示父子关系,下层节点先执行,上层消费下层结果。加上 ANALYZE 会真正执行语句并给出真实行数与耗时,加上 BUFFERS 能看到缓存命中情况,是排查性能问题最有效的组合。
基本信息
| 基本用法 | EXPLAIN SELECT … |
|---|---|
| 组合选项 | EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT … |
| cost=启动..总 | 启动成本是返回首行前的开销,总成本是全过程估算 |
| rows | 预估返回行数,不是扫描行数 |
| width | 预估输出行的平均字节宽度 |
| ACTUAL 行 | 只有加 ANALYZE 才出现,是实际执行数据 |
| 适用版本 | PostgreSQL 9.4 起支持 ANALYZE 与 BUFFERS 选项组合 |
典型使用场景
- 验证索引是否被使用:看节点是 Index Scan 还是 Seq Scan
- 比较两种写法的计划:改写前后各跑一次对照成本
- 定位估算偏差:ANALYZE 后对比 estimated 与 actual 行数
- 检查缓存命中:BUFFERS 显示 shared hit 与 read 的比例
- 确认连接方式:Nested Loop、Hash Join 还是 Merge Join
- 排查排序开销:计划里是否出现 Sort 节点与磁盘排序
示例
示例
EXPLAIN SELECT * FROM orders WHERE user_id = 100;
-- 输出示例:
-- Index Scan using idx_orders_user on orders (cost=0.43..16.85 rows=10 width=64)
-- Index Cond: (user_id = 100)
-- 真正执行并给出真实数据与缓存统计
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id = 100;
-- 输出会多出 actual time=0.021..0.045 rows=10 loops=1 与 Buffers: shared hit=4
-- 只估算不执行,适合查看写语句
EXPLAIN UPDATE orders SET status = 'done' WHERE id = 1;常见计划节点
| 节点 | 含义 | 关注点 |
|---|---|---|
| Seq Scan | 顺序扫描全表 | 小表正常,大表需检查索引 |
| Index Scan | 走索引并回表 | 回表行数多时可考虑覆盖索引 |
| Index Only Scan | 仅扫描索引不回表 | 可见性映射维护良好时最优 |
| Bitmap Heap Scan | 位图定位后再取行 | 范围条件常用 |
| Hash Join | 哈希连接 | 大表连接常见 |
| Nested Loop | 嵌套循环 | 被驱动表要有索引 |
| Sort | 排序节点 | 留意是否落盘 |
cost 是相对代价,不是耗时;不同实例的参数(如 random_page_cost)不同,跨实例比较绝对值没有意义,只看计划之间的相对变化。
EXPLAIN ANALYZE 会真正执行语句,写语句与重查询要谨慎使用,必要时包在事务里执行后回滚。
估算行数与实际行数差一个数量级时,往往是统计信息过期或数据分布倾斜,先 ANALYZE 该表再看计划是否改善。
排查要点
- 先看有没有 Seq Scan:大表上出现通常意味着索引缺失或条件不匹配
- 再看估算与实际的差距:差距大先更新统计信息
- 看连接方式是否合理:小结果集配 Hash Join 或 Nested Loop 各有适用场景
- 看 Sort 节点是否落盘:排序量大时会显著拖慢查询
- 用 BUFFERS 判断是缓存不足还是访问量过大
- 同一实例内做前后对比,跨实例只比结构性差异