全书目录

第八篇:PostgreSQL 完全指南 —— 时间档案馆宇宙

第十章:查询规划 —— 调度员决定你是翻目录还是翻整柜

2 分钟 626 字 第 174 / 962 个阅读单元

你写的是 SQL,但 PostgreSQL 真正执行前,会先做一件大事:

选执行计划。

也就是:

  • 走顺序扫描?
  • 走索引扫描?
  • 还是先位图、再回表?
  • Join 用嵌套循环、哈希连接,还是归并连接?

核心流程大致是:

text
SQL
 │
 ▼
解析 / 重写
 │
 ▼
规划器
 │   ├── 读取统计信息
 │   ├── 估算行数
 │   ├── 比较代价
 │   └── 选择计划
 ▼
执行器
 │
 ├── Seq Scan / Index Scan / Bitmap Heap Scan
 ├── Nested Loop / Hash Join / Merge Join
 └── 返回结果

最重要的工具是:

sql
EXPLAIN
SELECT *
FROM permits
WHERE citizen_id = 1
  AND status = 'active';

想看真实执行情况:

sql
EXPLAIN ANALYZE
SELECT *
FROM permits
WHERE citizen_id = 1
  AND status = 'active';

你经常会看到这些节点名:

  • Seq Scan:顺序扫整表
  • Index Scan:走索引逐条取
  • Bitmap Heap Scan:先从索引批量圈位置,再回表
  • Nested Loop:一层套一层找
  • Hash Join:先建哈希表再匹配
  • Merge Join:两边按顺序归并匹配

例如一个简化计划可能长这样:

text
Hash Join
  -> Seq Scan on permits
       Filter: (status = 'active')
  -> Hash
       -> Index Scan on citizens_pkey

这里千万别有一个误解:

“明明有索引,为什么 PostgreSQL 不用?”

因为规划器看的不是“有没有索引”,而是“用不用更划算”。

如果结果集本来就很大,顺序扫表反而可能更便宜。

规划器决策依赖统计信息,所以这几件事很重要:

  • ANALYZE 更新统计信息
  • VACUUM 回收旧版本并帮助维护可见性信息
  • autovacuum 不是可有可无,而是生产必需品

官方文档明确说明,VACUUM 的重要职责包括:

  • 回收或复用更新/删除留下的空间
  • 更新规划器要用的统计
  • 更新 visibility map
  • 防止 transaction ID wraparound

一句话记忆

慢查询不等于“缺索引”。 很多时候,问题是:

  • 统计过期
  • 过滤条件写得差
  • Join 顺序与数据分布不匹配
  • 一次查太多
  • 建了错索引