优化器与执行计划
PostgreSQL 使用基于代价的优化器。它根据统计信息估算各执行节点的行数和成本,再从可能的扫描、连接、聚合与并行方案中选择总代价较低的计划。成本是相对估值,不等于真实毫秒数。
优化链路
估算误差会沿计划树向上传播。底层扫描估错十倍,可能导致连接顺序、连接算法、并行度和内存策略全部发生偏差,因此优化应先找最早出现明显估算偏差的节点。
扫描方式
| 节点 | 访问方式 | 常见适用条件 |
|---|---|---|
| Seq Scan | 顺序读取 Heap 页面并过滤 | 需要大量行、小表或索引选择性低 |
| Index Scan | 通过索引定位并访问 Heap | 返回较少行,随机访问成本可接受 |
| Index Only Scan | 从索引取列,尽量通过 VM 跳过 Heap | 覆盖查询且页面大多 all-visible |
| Bitmap Index/Heap Scan | 汇总 TID 后按 Heap Page 批量访问 | 返回中等数量行或组合多个索引 |
看到 Seq Scan 不应直接得出“缺索引”。如果查询读取大部分表数据,顺序 I/O 可能比大量索引随机访问更便宜。
连接方式
| 方式 | 原理 | 常见条件 |
|---|---|---|
| Nested Loop | 外侧每行驱动内侧查询 | 外侧结果小,内侧有高选择性索引 |
| Hash Join | 为一侧建立哈希表,另一侧探测 | 等值连接、输入较大且哈希可控 |
| Merge Join | 两侧按连接键有序后归并 | 大量有序数据、范围或等值连接 |
Hash 或 Sort 超过可用工作内存时会写临时文件。work_mem 是每个执行节点的潜在预算,一个查询可同时存在多个 Sort 或 Hash,并发查询也会叠加,不能简单按总内存除以连接数后设置为很大值。
统计信息
ANALYZE 采样形成列分布统计,常见信息包括空值比例、Distinct 估算、Most Common Values、Histogram 和物理相关性。默认按单列统计,列之间存在强相关时,可使用 Extended Statistics 描述多列依赖、联合 Distinct 或常见值组合。
常见估算偏差来源:
- 统计信息过期或采样目标不足。
- 数据倾斜严重,常见值未被统计捕捉。
- 多列高度相关但优化器按独立概率估算。
- 表达式或类型转换使现有统计和索引无法匹配。
- 参数化查询使用通用计划,不适合某些参数分布。
阅读 EXPLAIN
EXPLAIN 显示估算计划;EXPLAIN ANALYZE 会真实执行语句,并显示实际行数和时间。对写语句执行 ANALYZE 会实际修改数据,诊断时应放在可回滚事务或安全环境中。
阅读顺序通常是:
- 从执行时间和数据量最大的节点定位主要成本。
- 比较 Estimated Rows 与 Actual Rows,找最早的数量级偏差。
- 结合 Buffers 判断命中缓存、物理读取和脏页写入。
- 检查 Filter Removed、Heap Fetches、Sort Spill 和循环次数。
- 再决定更新统计、调整 SQL、建立索引或修改数据模型。
不应长期通过关闭某种扫描或连接方式强迫优化器选计划。这类参数适合诊断假设,稳定修复应落在统计、索引、查询结构和数据分布上。