PostgreSQL 的 SQL 查询之旅

当输入 SELECT * FROM users WHERE id = 42;并执行时,这条看似简单的 SQL 语句,实际上会在 PostgreSQL 内部触发一段复杂而精密的处理流程。该过程涉及多个后台进程、精细的内存管理机制,以及数十年数据库优化研究的成果。

查询执行的五个阶段

无论查询复杂与否,在 PostgreSQL 中都会经历同一条基本路径:

解析(Parsing) → 分析(Analysis) → 重写(Rewriting) → 规划(Planning) → 执行(Execution)

SQL 文本从一端进入,查询结果从另一端返回。每一个阶段内部都包含大量细致而关键的处理逻辑。

查询的起点:SQL 发送过程

以示例查询语句为例,从提交时刻开始追踪整个执行过程。应用程序首先与 PostgreSQL 服务器建立连接,随后通过 PostgreSQL 通信协议发送查询请求。

需要重点关注的是:当发送语句 SELECT * FROM users WHERE id = 42;时,PostgreSQL 会原封不动地接收该纯文本格式的语句。无论通过 psql 终端输入、应用程序调用,还是借助 ORM 框架执行,SQL 语句最终都会以文本字符串的形式传递至服务器。

服务器在接收文本后,会进行基础校验,例如字符编码是否合法、语句格式是否完整。随后,正式进入查询处理流程。

第一阶段:解析 ------ 从文本到结构

解析器是查询处理的首个环节,核心任务是将 SQL 文本转换为结构化的解析树(Parse Tree)。

在该阶段,解析器会逐字符读取 SQL 语句,并依据 PostgreSQL 定义的 SQL 语法规则进行匹配和拆解,识别其中的关键字(如 SELECTFROMWHERE)、表名、列名、运算符等语法要素。

这一过程类似于语言学中的句法分析,只关注语法结构本身,而不涉及语义含义。

例如,SELECT name FROM users WHERE id = 42;解析完成后,系统可以明确:

  • 存在一个 SELECT 子句,包含列引用 name
  • 存在一个 FROM 子句,引用表 users
  • 存在一个 WHERE 子句,包含条件表达式 id = 42

但此时解析器并不知道 users 表是否真实存在、name 是否为有效列名,也不了解涉及字段的数据类型。这些语义层面的验证工作,将由下一阶段完成。

第二阶段:分析 ------ 语义校验与绑定

分析器在解析树的基础上,构建语义有效的查询树(Query Tree),这是从"语法正确"迈向"语义正确"的关键阶段。

该阶段主要完成以下工作:

  • 对象解析 :在系统目录中查找 users 表,校验其是否存在;确认 nameid 是否为合法列。
  • 类型检查 :校验 id = 42 是否成立,例如 id 的数据类型是否支持与整数进行比较,对应的比较运算符是否存在。
  • 权限校验 :确认当前会话是否具备访问 users 表及相关列的 SELECT 权限。
  • 语义信息补充:为查询树补充对象标识信息,如表和列对应的 OID、字段类型等。

若任一环节失败(表不存在、字段拼写错误、权限不足等),查询将在此阶段终止并返回错误。

完成该阶段后,查询的含义已被 PostgreSQL 完整理解,接下来进入自动转换处理。

第三阶段:重写 ------ 自动规则转换

重写器在语义有效的查询树基础上,应用一系列自动化转换规则,生成最终待执行的查询结构。常见的转换包括:

  • 视图展开 :当查询对象为视图时,重写器会将视图查询转换为对底层基表的查询。例如,若视图 active_users 的定义为 SELECT * FROM users WHERE status = 'active',则查询 SELECT * FROM active_users 会被重写为直接查询 users 表并附加过滤条件 status = 'active'
  • 行级安全策略(RLS) :若表定义了安全策略,重写器会自动注入额外的 WHERE 条件以实现访问控制。例如,存在按租户隔离数据的策略时,原查询 SELECT * FROM users WHERE id = 42 可能被重写为 SELECT * FROM users WHERE id = 42 AND tenant_id = 123
  • 用户自定义规则:通过规则系统定义的查询重写逻辑(在现代应用中相对较少使用)。

对于简单查询,该阶段可能不会发生明显变化;但在包含视图、安全策略的复杂系统中,重写可能对查询结构产生显著影响。

第四阶段:规划 ------ 寻找最优执行路径

规划器负责解决一个核心问题:如何以最低成本执行该查询。

这一阶段是 PostgreSQL 中最复杂、最具智能化特征的部分,涉及多维度决策。

访问路径选择

对于查询中涉及的每张表,规划器需要决定数据的读取方式。例如,是对 users 表执行全表顺序扫描,还是利用 id 字段上的索引直接定位目标数据行。规划器需要决定采用何种方式访问数据:

  • 顺序扫描(Sequential Scan)
  • 索引扫描(Index Scan / Bitmap Scan)

规划器会综合评估表规模、索引可用性及选择性。有时,即便存在索引,顺序扫描也可能更高效。

连接策略选择

在多表查询中,规划器需同时确定:

  • 表的连接顺序
  • 每一步连接所采用的算法

连接顺序的影响至关重要。例如,先连接表 A 和表 B,再与表 C 连接,和先连接表 B 和表 C,再与表 A 连接,两种方式的执行效率可能存在巨大差异。规划器会评估多种连接顺序,筛选出最优方案。

针对每个连接操作,PostgreSQL 支持的主要连接算法包括:

  • 嵌套循环连接(Nested Loop):适用于小数据集,或当连接操作的其中一方数据量极少的场景。
  • 哈希连接(Hash Join):在内存充足的情况下,对中大型数据集的连接操作具有较高效率。
  • 归并连接(Merge Join):适用于两个输入数据集均已排序的场景。

不同连接顺序和算法组合,对整体性能影响巨大,规划器会评估多种可能性。

统计信息的作用

所有规划决策高度依赖统计信息。PostgreSQL 通过 ANALYZE 收集并维护表统计数据,包括:

  • 表的总行数
  • 各字段的不同值数量
  • 数据分布情况

这些信息用于估算过滤条件和连接操作的结果规模,是成本评估的基础。统计信息不准确将直接导致规划决策偏差。

成本估算与最终计划

规划器会对多种候选执行计划进行成本估算,综合考虑:

  • 磁盘 I/O 成本(从磁盘或缓存中读取数据页)
  • CPU 计算成本(数据行处理、条件表达式计算)
  • 内存消耗(排序、哈希操作)

最终选择成本最低的方案作为执行计划。当查询涉及大量表连接时,PostgreSQL 会启用遗传查询优化器(Genetic Query Optimizer)以避免组合爆炸。

规划结果可通过 EXPLAIN 查看,例如:

复制代码
EXPLAIN SELECT name FROM users WHERE id = 42;

第五阶段:执行 ------ 生成结果

执行器依据执行计划逐步获取数据,并向客户端返回结果。PostgreSQL 采用拉取式(Pull-based)执行模型。

在该模型中,上层节点按需向下层节点请求数据,而非由下层主动推送。这种机制具备良好的内存效率,并天然支持 LIMIT 等提前终止操作。

仍以查询 SELECT name FROM users WHERE id = 42 为例,其执行计划的输出结果可能如下:

复制代码
                        QUERY PLAN
-----------------------------------------------------------
 Index Scan using users_id_idx on users  (cost=0.00..8.27 rows=1 width=64)
   Filter: (id = 42)
(2 rows)

以示例查询为例,执行流程为:

  1. 执行计划的顶层节点(负责向客户端返回结果)发起数据行请求。
  2. 该请求触发索引扫描节点,向其下层节点发起数据请求。
  3. 索引扫描节点利用 users_id_idx 索引定位满足条件 id = 42 的数据行。
  4. 从磁盘或缓存中读取目标数据行,并应用过滤条件进行校验。
  5. 数据结果沿执行计划逆向传递:索引扫描节点 → 客户端。

拉取式模型具有显著的内存效率优势,因为 PostgreSQL 仅在下游节点需要数据时才执行处理操作。同时,该模型也简化了结果集限制与提前终止等功能的实现 ------ 只需停止向上游节点请求数据即可。

执行器逐行处理数据,按照通信协议的要求格式化结果,然后通过网络连接将数据发送至客户端。

所有结果发送完成后,PostgreSQL 会自动执行清理操作:

  • 销毁临时内存上下文
  • 释放执行过程中获取的各类锁资源
  • 删除执行期间生成的临时文件
    至此,当前后端处理进程恢复空闲状态,准备接收下一个查询请求。

全流程回顾

SELECT name FROM users WHERE id = 42 为例,完整流程如下:

  1. 查询发送:应用程序建立连接,以纯文本形式发送查询语句。
  2. 解析:将文本转换为解析树,完成语法结构校验。
  3. 分析:验证语义合法性,补充元数据信息,生成查询树。
  4. 重写:执行自动转换操作,如视图展开、安全策略应用。
  5. 规划:评估访问路径、连接策略,结合统计信息生成最优执行计划。
  6. 执行:基于拉取式模型执行计划,生成并返回查询结果。
  7. 清理:释放资源,恢复进程空闲状态。

所有查询均遵循该路径,区别仅在于各阶段的复杂程度。

核心价值

理解 PostgreSQL 查询执行流程,能够带来以下核心价值:

  1. 编写更高效的查询语句:掌握规划器的工作原理,可针对性地优化查询结构,例如理解部分查询无法使用索引的原因、连接顺序对性能的影响,以及公用表表达式(CTE)与子查询的适用场景差异。
  2. 精准诊断性能问题:当查询执行缓慢时,可通过 EXPLAIN 命令定位问题环节,判断是规划器选择了低效执行路径、统计信息过期,还是缺少必要的索引。
  3. 设计更合理的数据库架构:基于查询执行机制的理解,能够优化索引设计、表分区方案以及视图的使用方式。
  4. 认知数据库底层复杂度:一条简单的 SELECT 语句背后,隐藏着连接管理、内存分配、语法校验、语义分析、规则应用、成本优化以及拉取式执行等一系列复杂机制,而这一切都在后台透明运行。

原文链接:

https://internals-for-interns.com/posts/sql-query-roadtrip-in-postgres/

作者:Jesús Espino

相关推荐
文心快码BaiduComate26 分钟前
百度云与光本位签署战略合作:用AI Agent 重构芯片研发流程
前端·人工智能·架构
风象南1 小时前
Claude Code这个隐藏技能,让我告别PPT焦虑
人工智能·后端
Mintopia2 小时前
OpenClaw 对软件行业产生的影响
人工智能
陈广亮2 小时前
构建具有长期记忆的 AI Agent:从设计模式到生产实践
人工智能
会写代码的柯基犬3 小时前
DeepSeek vs Kimi vs Qwen —— AI 生成俄罗斯方块代码效果横评
人工智能·llm
Mintopia3 小时前
OpenClaw 是什么?为什么节后热度如此之高?
人工智能
爱可生开源社区3 小时前
DBA 的未来?八位行业先锋的年度圆桌讨论
人工智能·dba
叁两6 小时前
用opencode打造全自动公众号写作流水线,AI 代笔太香了!
前端·人工智能·agent
前端付豪6 小时前
LangChain记忆:通过Memory记住上次的对话细节
人工智能·python·langchain