执行计划与调优
调优的第一步不是「加索引」,而是看懂查询是怎么执行的。EXPLAIN 让你看到规划器选了什么路径、预估了多少行、花了多少代价。
1. EXPLAIN 基础
EXPLAIN SELECT * FROM core.orders WHERE user_id = 1;不加 ANALYZE 时只显示规划器的预估(不真正执行)。想知道真实耗时和真实行数,加 ANALYZE:
EXPLAIN ANALYZE SELECT * FROM core.orders WHERE user_id = 1;建议常用组合:EXPLAIN (ANALYZE, BUFFERS) 还能看到缓冲命中/磁盘读,定位是不是在「读磁盘」。
2. 常见节点类型
| 节点 | 含义 |
|---|---|
Seq Scan | 全表顺序扫描,小表无所谓,大表是性能杀手 |
Index Scan | 走索引取行(随机 IO 多) |
Index Only Scan | 索引里就有答案,不回表,最快 |
Bitmap Heap Scan | 先用索引位图定位,再批量回表,介于两者之间 |
Nested Loop | 小表驱动大表,适合一方很小 |
Hash Join | 一方建哈希表,适合无索引的等连接 |
Merge Join | 两边有序时归并,适合大表等连接 |
3. 识别问题
- 大表上出现
Seq Scan且rows预估偏差大 → 缺索引或统计信息过期。 actual rows远多于estimated rows→ 统计信息不准,跑ANALYZE 表名。- 节点
cost/actual time异常高 → 该节点是瓶颈,针对性优化。
4. 让规划器更聪明
规划器靠统计信息估算行数与代价。信息过期会导致它选错路径:
ANALYZE core.orders; -- 立即收集统计信息
SET work_mem = '64MB'; -- 给排序/哈希更多内存,减少落盘几个关键参数(改在 postgresql.conf,或会话级 SET):
work_mem:每个排序/哈希操作的内存,调大可避免落临时文件(但会乘并发数,别太大)。random_page_cost:随机 IO 代价,SSD 上可下调(如 1.1),让规划器更愿用 Index Scan。effective_cache_size:告诉规划器「系统有多少内存可用于缓存」,设大些会更倾向索引。shared_buffers:共享缓冲,一般设为物理内存 25%。
5. 调优标准流程
- 用
EXPLAIN ANALYZE找到最慢的节点。 - 检查是否缺索引(加 b-tree/组合/部分/表达式索引)。
- 检查统计信息是否准(
ANALYZE)。 - 重写 SQL(避免
SELECT *、把能下推的条件提前过滤、用JOIN而非子查询嵌套)。 - 调参数(缓存、work_mem、代价系数)。
⚠️EXPLAIN 不执行,ANALYZE 才执行
EXPLAIN 只规划不执行,安全;EXPLAIN ANALYZE 会真正跑,对写入查询(很少在 EXPLAIN 里写)要小心。线上排查慢查询,先用 EXPLAIN,确认无害再上 ANALYZE。
🎯动手
对 SELECT * FROM core.orders WHERE user_id = 1 AND status='paid' 跑 EXPLAIN ANALYZE,观察用的是 Seq Scan 还是 Index Scan。然后建第 17 章讲的组合索引 (user_id, status) 再 EXPLAIN 一次,对比计划变化。