首页 > 数据库 > PostgreSQL > PostgreSQL 的 SQL 查询之旅
当输入
SELECT * FROM users WHERE id = 42;并执行时,这条看似简单的 SQL 语句,实际上会在 PostgreSQL 内部触发一段复杂而精密的处理流程。该过程涉及多个后台进程、精细的内存管理机制,以及数十年数据库优化研究的成果。
无论查询复杂与否,在 PostgreSQL 中都会经历同一条基本路径:
解析(Parsing) → 分析(Analysis) → 重写(Rewriting) → 规划(Planning) → 执行(Execution)
SQL 文本从一端进入,查询结果从另一端返回。每一个阶段内部都包含大量细致而关键的处理逻辑。
以示例查询语句为例,从提交时刻开始追踪整个执行过程。应用程序首先与 PostgreSQL 服务器建立连接,随后通过 PostgreSQL 通信协议发送查询请求。
需要重点关注的是:当发送语句
SELECT * FROM users WHERE id = 42;时,PostgreSQL 会原封不动地接收该纯文本格式的语句。无论通过 psql 终端输入、应用程序调用,还是借助 ORM 框架执行,SQL 语句最终都会以文本字符串的形式传递至服务器。
服务器在接收文本后,会进行基础校验,例如字符编码是否合法、语句格式是否完整。随后,正式进入查询处理流程。
解析器是查询处理的首 个环节,核心任务是将 SQL 文本转换为结构化的解析树(Parse Tree)。
在该阶段,解析器会逐字符读取 SQL 语句,并依据 PostgreSQL 定义的 SQL 语法规则进行匹配和拆解,识别其中的关键字(如
SELECT、
FROM、
WHERE)、表名、列名、运算符等语法要素。
这一过程类似于语言学中的句法分析,只关注语法结构本身,而不涉及语义含义。
例如,
SELECT name FROM users WHERE id = 42;解析完成后,系统可以明确:
name。users。id = 42。但此时解析器并不知道
users 表是否真实存在、
name 是否为有效列名,也不了解涉及字段的数据类型。这些语义层面的验证工作,将由下一阶段完成。
分析器在解析树的基础上,构建语义有效的查询树(Query Tree),这是从“语法正确”迈向“语义正确”的关键阶段。
该阶段主要完成以下工作:
users 表,校验其是否存在;确认
name、
id 是否为合法列。id = 42 是否成立,例如
id 的数据类型是否支持与整数进行比较,对应的比较运算符是否存在。users 表及相关列的 SELECT 权限。若任一环节失败(表不存在、字段拼写错误、权限不足等),查询将在此阶段终止并返回错误。
完成该阶段后,查询的含义已被 PostgreSQL 完整理解,接下来进入自动转换处理。
重写器在语义有效的查询树基础上,应用一系列自动化转换规则,生成最终待执行的查询结构。常见的转换包括:
active_users 的定义为
SELECT * FROM users WHERE status = 'active',则查询
SELECT * FROM active_users 会被重写为直接查询
users 表并附加过滤条件
status = 'active'。SELECT * FROM users WHERE id = 42 可能被重写为
SELECT * FROM users WHERE id = 42 AND tenant_id = 123。对于简单查询,该阶段可能不会发生明显变化;但在包含视图、安全策略的复杂系统中,重写可能对查询结构产生显著影响。
规划器负责解决一个核心问题:如何以最低成本执行该查询。
这一阶段是 PostgreSQL 中最复杂、最 具智能化特征的部分,涉及多维度决策。
对于查询中涉及的每张表,规划器需要决定数据的读取方式。例如,是对
users 表执行全表顺序扫描,还是利用
id 字段上的索引直接定位目标数据行。规划器需要决定采用何种方式访问数据:
规划器会综合评估表规模、索引可用性及选择性。有时,即便存在索引,顺序扫描也可能更高效。
在多表查询中,规划器需同时确定:
连接顺序的影响至关重要。例如,先连接表 A 和表 B,再与表 C 连接,和先连接表 B 和表 C,再与表 A 连接,两种方式的执行效率可能存在巨大差异。规划器会评估多种连接顺序,筛选出最优方案。
针对每个连接操作,PostgreSQL 支持的主要连接算法包括:
不同连接顺序和算法组合,对整体性能影响巨大,规划器会评估多种可能性。
所有规划决策高度依赖统计信息。PostgreSQL 通过
ANALYZE 收集并维护表统计数据,包括:
这些信息用于估算过滤条件和连接操作的结果规模,是成本评估的基础。统计信息不准确将直接导致规划决策偏差。
规划器会对多种候选执行计划进行成本估算,综合考虑:
最终选择成本最低的方案作为执行计划。当查询涉及大量表连接时,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)
以示例查询为例,执行流程为:
拉取式模型具有显著的内存效率优势,因为 PostgreSQL 仅在下游节点需要数据时才执行处理操作。同时,该模型也简化了结果集限制与提前终止等功能的实现 —— 只需停止向上游节点请求数据即可。
执行器逐行处理数据,按照通信协议的要求格式化结果,然后通过网络连接将数据发送至客户端。
所有结果发送完成后,PostgreSQL 会自动执行清理操作:
以
SELECT name FROM users WHERE id = 42 为例,完整流程如下:
所有查询均遵循该路径,区别仅在于各阶段的复杂程度。
理解 PostgreSQL 查询执行流程,能够带来以下核心价值:
原文链接:
https://internals-for-interns.com/posts/sql-query-roadtrip-in-postgres/
作者:Jesús Espino
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70043484/viewspace-3107218/ ,如需转载,请注明出处,否则将追究法律责任。