上一篇文章把 PostgreSQL 从接收 SQL 到完成语法分析、语义分析的过程梳理了一遍。这次继续往下走,重点讲从 Query 树到执行计划、再从执行计划到真正把数据行返回给客户端这一段。简单说,上一篇讲的是“读懂你在说什么”,这一篇讲的是“决定怎么做”和“真的做出来”。
如果你写过几年 SQL,却不清楚为什么有时候加了索引反而慢、为什么 EXPLAIN 里的 cost 数字那么大、为什么一条小查询会触发并行执行,那这篇文章正好能补上这些盲区。对 DBA、后端开发、以及想搞懂数据库内部机制的初学者都很合适。内容不会太偏向内核源码,我会尽量用实际操作中的经验来讲清楚 PostgreSQL 的 SQL 执行过程,顺便把 EXPLAIN 怎么看、慢 SQL 怎么下手这类问题一并聊透。
1. SQL 声明之后:优化器为何是执行过程的分水岭
1.1 从语法树到 Query 树,上一站在哪里
先说清楚一个容易混淆的点:语法分析结束后,PostgreSQL 得到的并不是一张可以直接执行的“流程清单”,而是一棵分析树。这棵树里记录了表名、列名、WHERE 条件、JOIN 关系等,但它仍然是完全面向“你写了什么”的,和“计算机应该怎么找数据”没有任何关系。
接下来进入语义分析阶段,PostgreSQL 会把分析树转换成一棵 Query 树。这一步会做不少正经事:检查表是否存在、列是否存在、根据当前搜索路径解析未加 schema 限定的表名、确定函数和操作符的实际类型,还会把 SELECT * 展开成具体的列清单。
等 Query 树出来后,后面的大头才登场:重写器、优化器和执行器。第一篇如果只讲到了语法树,那从这里开始就是我们这次要展开的全部内容。你在 pg_stat_statements 里看到一条 SQL 的调用次数和平均耗时,背后也是这条整链路消耗的总时间。
1.2 规则重写:把真实意图先还原出来
很多初学者不知道 PostgreSQL 在执行前还有一个重写阶段。规则系统平时不太容易被感知,但它一直在背后干活。最常见的一个场景就是视图。
你写 SELECT * FROM v_user_order,v_user_order 在数据库里其实是一条已保存的 SELECT 定义。重写器拿到你的查询后,会把视图引用展开成视图对应的子查询,再和你的条件组合。看起来你是查一张“表”,实际执行时可能已经变成两张表甚至更多张表的 JOIN 了。这也是为什么有时候视图查起来很慢,因为真实查询可能比你想的要复杂得多。
除了视图展开,重写器还负责规则触发,比如你可以用 CREATE RULE 对某个表定义插入、更新或删除的替代行为。虽然规则系统的使用场景不算多,但理解它的存在有助于排查一些“我明明改了表数据,为什么没有生效”的诡异问题。更重要的结论是:执行过程并不是从你的原始 SQL 直接开始的,而是从一个经过改写和展开之后的 Query 树开始的。
1.3 优化器不像执行器那样“听话”
如果数据库直接按照 SQL 文本字面意思去执行,那结果很多时候会是一场灾难。比如你写:
sql复制SELECT *
FROM orders
WHERE customer_id = 1024;
理论上先全表扫描 orders,再逐行判断 customer_id 是否等于 1024,也能得到正确结果。但如果 orders 表已经有两千万行,每次查询都全表扫一遍,报表业务根本没法用。
SQL 是声明式语言,你要的是结果集合,而不是执行步骤。执行细节怎么安排,完全是优化器和执行器的事。PostgreSQL 的优化器会考虑许多可能的访问路径,比如按主键索引取行、按普通索引回表、用位图扫描、或者干脆顺序扫描整张表,然后挑一个它认为“成本最低”的计划交给执行器。这个决策的过程,就是一条 SQL 能不能跑得快的关键分水岭。
很多人在应用层骂“数据库怎么这么傻,有索引不用”,其实很可能问题就出在统计信息不准确、或者成本估算与实际执行相差太大,而不是数据库本身逻辑简单。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 路径与代价:优化器如何做出执行决策
2.1 统计信息是执行决策的底牌
优化器做决策不能靠猜,它得有依据。PostgreSQL 在每个表上维护的统计信息,就是它做所有估算的重要底牌。
这些统计信息主要存储在 pg_class 和 pg_statistic 等系统目录中。pg_class.relpages 记录表大约占多少数据页,reltuples 记录大约有多少行。pg_statistic 则保存每个字段的详细分布情况,包括最常见的值(MCV)、直方图边界、空值比例、平均字段宽度等等。但 pg_statistic 默认不允许普通用户直接读取,日常你通过 pg_stats 视图就能看到比较完整的统计。
统计信息准不准,直接决定执行计划靠不靠谱。我经历过一次印象很深的故障:一张流水表每天半夜会批量插入几百万行,但统计信息没有及时更新。第二天白天某个报表查询上来时,优化器还傻乎乎地以为这张表只需要扫描 1000 个数据
