explain analyze必须使用,否则无法获取实际耗时、扫描行数、缓冲区命中率等关键性能指标;它真实执行sql,可暴露nested loop是否误用全表扫描、hash join是否溢出磁盘等问题,是精准调优的唯一可靠依据。

直接看 EXPLAIN ANALYZE,不加它等于盲调。 PostgreSQL 的执行计划默认只估算,不实际跑,很多 JOIN 策略(比如 Nested Loop 是否真用了索引、Hash Join 的 Build 阶段是否爆内存)只有加 ANALYZE 才暴露真实行为。
为什么只用 EXPLAIN 会误判 JOIN 性能
默认 EXPLAIN 只输出成本估算和计划结构,但关键指标全缺失:没有实际耗时、没有扫描行数、没有缓存命中率、不知道哪一步卡住。比如一个 Nested Loop 看起来成本低,实际可能因内表没走索引,变成 O(N × M) 暴力扫——这种陷阱只在 ANALYZE 后的 Actual Time 和 rows 里浮现。
- 必须加
ANALYZE:否则Buffers、Actual Time、rows这些救命字段全为空 - 建议连用
BUFFERS:看shared hit和read比例,判断是否频繁刷盘 - 慎用
TIMING:在高并发生产库上可能引入微小扰动,调试阶段开,压测阶段可关
Nested Loop 真的慢吗?先看它连的是谁
PostgreSQL 的 Nested Loop 不是洪水猛兽,它在“小外表 + 大内表 + 内表有索引”时反而是最优解。问题常出在优化器误判了外表大小,或内表根本没走索引。
- 检查
Index Cond是否出现:没这行,说明内表被当全表扫了 - 对比
rows预估 vs 实际:如果预估 100 行,实际返回 50 万行,说明统计信息过期,该跑VACUUM ANALYZE - 留意
Join Filter:如果这里出现条件,说明 JOIN 条件无法下推到索引扫描,被迫在循环里逐行过滤
JOIN 顺序不对?用 STRAIGHT_JOIN 强制不了,得换写法
PostgreSQL 没有 STRAIGHT_JOIN,不能强制连接顺序。但你可以通过子查询或 CTE 把关键小表“提前固化”,变相控制流程:
- 把过滤后确定很小的结果集(如
WHERE status = 'active' LIMIT 100)抽成WITH子句,再跟大表JOIN - 避免在
ON条件里调用函数(如ON t1.id = my_hash(t2.code)),这会让优化器放弃索引,还可能触发全表计算 - 如果发现优化器总选错顺序,检查
join_collapse_limit参数,默认是 8;超过这个数的 JOIN 会被强制按书写顺序执行,但前提是你写的顺序本身得合理
查完执行计划,下一步盯紧三个地方
执行计划不是终点,而是定位点。真正卡顿往往藏在细节里:
-
Seq Scan出现在本该走索引的大表上 → 立刻检查 WHERE 条件字段是否有 B-tree 索引,以及是否写了 Sargable 表达式(比如别写WHERE UPPER(name) = 'JOHN') -
Hash Join的Buffers: temp read=xxx很高 → work_mem 不够,哈希表溢出到磁盘,该调大work_mem或拆分 JOIN - 递归 CTE 里 JOIN 方向写反(比如查祖先却写成
ON c.id = tp.parent_id)→ 结果静默错误,不报错但路径全空,必须人工核对逻辑方向










