你写的是 SQL,但 PostgreSQL 真正执行前,会先做一件大事:
选执行计划。
也就是:
- 走顺序扫描?
- 走索引扫描?
- 还是先位图、再回表?
- Join 用嵌套循环、哈希连接,还是归并连接?
核心流程大致是:
SQL
│
▼
解析 / 重写
│
▼
规划器
│ ├── 读取统计信息
│ ├── 估算行数
│ ├── 比较代价
│ └── 选择计划
▼
执行器
│
├── Seq Scan / Index Scan / Bitmap Heap Scan
├── Nested Loop / Hash Join / Merge Join
└── 返回结果最重要的工具是:
EXPLAIN
SELECT *
FROM permits
WHERE citizen_id = 1
AND status = 'active';想看真实执行情况:
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:两边按顺序归并匹配
例如一个简化计划可能长这样:
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 顺序与数据分布不匹配
- 一次查太多
- 建了错索引