尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
深度解读PostgreSQL执行器:从README到源码实践
前一段日子为了彻底搞懂PostgreSQL执行器的工作机制我做了一件挺小众的事把源码里src/backend/executor/README从头到尾逐段翻译了一遍。这份README可以说是PG源码中最接近“执行器设计白皮书”的文档但我翻了很久中文社区几乎没有看到对它的完整解读和翻译大部分文章不是在抄外文博客就是只讲了EXPLAIN的皮毛。今天这篇文章我就把这次翻译过程中整理出的执行器知识框架、术语处理方案、文档与代码交叉验证的一线经验一起分享出来。如果你正准备入门PostgreSQL内核或者想搞明白一条SQL从计划到结果这条路上到底发生了什么这篇应该对你有用。1. 我为什么非要啃这份README源码深处的门钥匙1.1 在代码注释里迷路之后PostgreSQL的执行器代码量非常大src/backend/executor/目录下几十个C文件execMain.c、execProcnode.c、execExpr.c、nodeSeqscan.c、nodeNestloop.c……每个文件都动辄上千行。我最初试图靠“注释断点”的方式去读结果被整懵了单个节点文件里的注释通常只解释“我自己在做什么”不会告诉你它和上层、下层模块之间是怎么咬合的。就像拿到一堆齿轮每个齿轮上都刻着“我是几号齿轮”但没有装配图。后来有经验的同事提醒我先去读executor/README。这份文档是PG社区的核心开发者Tom Lane等人写的用非常精练的语言描述了执行器整体的设计思路从ExecutorStart到ExecutorEnd的生命周期、三层状态结构、元组在节点之间流动的机制、表达式求值如何组织以及并行执行、子计划等进阶话题。读完它再回去看代码很多“为什么这么写”的疑问都能迎刃而解。翻译的过程其实就是在强制自己把这份装配图逐行读懂不放过任何一句含糊的话。1.2 翻译前必须做的前置工作翻译这份README之前我花了一天时间做准备工作这一步非常值得也建议想自己动手读源码的人照做锁定版本我基于PostgreSQL 16的源码进行翻译。PG各版本之间执行器的改动不算小比如并行执行、分区表相关的结构在不同版本差异明显README也有对应变化。如果你在阅读时发现某些描述和手头代码对不上先检查版本是否一致。粗略通读原文先从头到尾快速扫一遍了解文档覆盖哪些主题标记出完全不懂的段落。第一遍通读我大概花了四十分钟虽然很多细节没吃透但整体框架已经建立起来了。准备代码环境本地需要一份完整源码并且最好能编译出一个能跑的实例。翻译到任何有疑问的地方我都可以直接grep对应结构定义或者在src/backend/executor/里搜函数实现来验证。建立术语草表把文档里反复出现的高频词先列出来比如tuple、slot、scan、join、projection、subplan、estate在翻译初期就先确定译法避免后面前后不一致。准备工作做完翻译本身反而成了最轻松的部分真正花时间的是逐字逐句对照代码去理解每一段话在说什么。2. README里的执行器地图必须搞懂的几张关系图2.1 执行器在整个SQL处理链路里的位置理解执行器先要把它放到PG处理一条SQL的完整链路中看。执行器不是孤立的它是这条流水线的一个核心环节Parser把SQL文本转换成词法语法树也就是RawStmt级别的解析树。Analyzer做语义分析绑定对象把解析树变成查询树Query。Rewriter处理视图展开、规则重写。视图本质上就是在这个阶段被展开成底层表查询的。Planner生成执行计划也就是一棵Plan节点树。这棵树描述了“用什么算法、按什么顺序扫描和连接表”。Executor真正执行这棵Plan树逐行产生结果返回给上层。翻译README时我最大的顿悟就是Planner输出的是静态的“计划图纸”Executor负责把图纸变成动态运行的“机器”。Plan节点树上的每个节点都描述“做什么”但真正干活需要与之配套的“运行状态”。这就是README一开始就反复强调的PlanState。从整体架构来看执行器是连接计划与存储的桥梁但它并不直接做存储管理而是通过存储管理器接口读取表数据和索引。上层看到的是统一的行流底层具体的文件格式、缓冲管理、锁机制对执行器来说都是透明的。2.2 三层状态结构estate、PlanState和TupleTableSlotREADME最核心的部分之一就是介绍执行器的三层状态结构这是理解执行器的钥匙。第一层是ExecutorState通常命名为estate它保存整个查询执行期间都有效的全局信息比如内存上下文、范围表、参数值、被修改的目标关系列表、tuple table等。你可以把它理解成整个执行过程的“总台账”。第二层是PlanState树。Planner生成Plan树后ExecInitNode会递归遍历这棵树为每个Plan节点生成对应的PlanState节点形成一棵并行的状态树。PlanState里保存了执行当前节点需要的局部运行状态包括当前的结果元组、扫描描述符、投影信息、子节点状态指针等。这种设计把“静态计划”和“动态状态”彻底分离非常优雅。也就是说同一个Plan可以被重复初始化成多个PlanState实例这为后来并行执行、多次执行计划提供了很大便利。第三层是TupleTableSlot。执行器在节点之间传递数据时使用的不是裸指针而是一个统一包装结构TupleTableSlot。为什么用slot因为不同数据源的数据形态差别很大堆表读出的是磁盘上的元组索引扫描可能返回的是索引元组连接操作生成的是在内存中拼接的新元组聚合算子则产生的是最终结果行。TupleTableSlot通过一个tts_ops函数表抽象了这些差异统一提供了“取字段、拷贝元组、清理资源”等操作接口。节点之间交互只感知slot不需要感知底层数据到底是从哪里来的。在这三层结构之上所有节点共享同一个调度入口ExecProcNode。它接收一个PlanState指针返回一个TupleTableSlot代表该节点产生的“下一行”。如果返回空值说明该节点数据已经耗尽。每个具体节点扫描节点、连接节点、聚合节点都实现自己的ExecXXX函数但对外暴露的接口完全统一。正是这个统一接口让执行器可以像递归管道一样一层一层把数据从叶子节点抽到根节点最终由顶层节点把结果元组输出给客户端。2.3 投影、表达式求值与内存上下文的配合README里还有一个反复出现的概念是投影projection。我花了不少时间才真正理解它在执行器里的含义。简单来说投影就是“从当前元组中取出需要的列按目标列表组成新元组”的过程。比如你SELECT a, b FROM t WHERE c 10扫描节点读出的是完整的表行包含a、b、c三列但最终交给上层的元组只需要a、b两列。执行器在扫描节点上挂一个ExprState用于做投影每个节点都可以有自己的投影逻辑把宽元组“压”成窄元组再向上传递。投影和表达式求值是执行器性能的关键热点。PG早期版本用递归的方式每个表达式节点逐个求值效率一般后来引入了ExprState这个编译后的表达式执行结构把表达式树转换成更高效的执行步骤。到了PG 12之后又引入JIT编译能力部分热路径上的表达式会被直接编译成机器码。翻译到这一节时我忍不住感慨一个看起来简单的WHERE id 100在底层其实经历了表达式状态初始化、参数绑定、字段抽取、比较运算、结果布尔化等多个环节而这些都被README用一小段话概括了。执行器的另一条生命线是内存上下文。PG中无论是PlanState节点、TupleTableSlot还是表达式状态都创建在特定的内存上下文中。一个查询执行期间产生的临时内存会被统一分配在estate对应的上下文中在查询结束后一次性回收。这样避免了频繁malloc/free导致的内存碎片也让资源清理变得异常简单。翻译README时这部分让我对PG“一次查询一个内存世界”的设计有了切身体感。3. 翻译过程踩过的坑术语一致性是最大的敌人3.1 从“tuple到底该叫什么”说起我最早犯的错是在不同章节里把tuple分别翻译成“元组”和“行”。第一遍翻译时觉得上下文自然就行结果回读时发现非常混乱有些章节写“元组通过slot传递”有些章节又写“行被投影后返回”。读者很容易搞混。后来我强制自己统一tuple一律译为“元组”row如果出现译为“行”table译为“表”。为什么不用“行”因为在PG的执行器术语体系里元组是比行更通用的概念它可能是堆表里的物理元组、索引元组、内存拼接元组叫“行”容易让人误以为都是物理行。翻译长篇技术文档术语一致性不是风格问题而是准确性问题。类似的高频词还有slot固定译为“槽”或“slot”考虑中文不常用“槽”我保留“slot”并首次出现时加注中文说明。scan统一为“扫描”。join统一为“连接”不译成“联结”或“关联”。projection统一为“投影”。estate不翻译保留原词。建立术语表带来的直接好处是翻译到后面遇到同类句子几乎可以机械化套用效率明显提升而且回读、校对时不再需要来回猜测。3.2 result relation目标关系还是结果关系这次翻译中我最有分歧感的一个词是result relation。如果按字面直译是“结果关系”但读代码你就会发现不对劲。result relation指的是DML语句中要被修改的目标表对INSERT来说是要插入数据的表对UPDATE/DELETE来说是要被更新的表。它不是“结果存储的关系”而是“操作施加的对象”。因此在译文中我采用了**“目标关系”**的译法并在译者注里说明它等同于“被更新的表”。这类词如果不加判断直译简直会带偏所有读译文的人。还有一个容易被忽略的术语是qual它其实是qualification的缩写指的是过滤条件。这个词在代码注释和文档中大量出现如果直译成“限定”中文读者基本看不懂。我统一译为“过滤条件”并在术语表中注明原词。3.3 README滞后于代码版本不一致的排查链路翻译到并行执行相关章节时我发现README中描述的结构和PG 16源码明显对不上。比如文档还在描述某个旧字段而代码里已经改成了新的状态机。这种情况让我一度怀疑是自己理解错了后来通过下面这套排查链路解决了先用git log --oneline -- src/backend/executor/README查看这个文件的修改历史确认README是哪个时期写的。再用git blame定位到具体行看是哪次提交改了这块描述。对应打开源码文件比如execParallel.c或nodeAgg.c看当前版本实际使用的结构。最后在译文里加“译者注”明确说明“此段描述基于PG某版本PG16中已改为XXX”。这个过程特别像一次小型的代码考古。处理完我才意识到翻译开源项目文档本质上是在做版本对齐和语义验证纯粹的语言转换只是很小的一部分。遇到任何一句看起来“不太对劲”的话都值得停下来去源码里确认而不是硬翻过去。4. 翻译之外的最大收获用README反推代码执行细节4.1 拿EXPLAIN ANALYZE去验证README描述读完README我做的第一件事就是建一张测试表跑EXPLAIN ANALYZE来验证文档里描述的执行流程。比如执行EXPLAIN ANALYZE SELECT id, name FROM users WHERE age 25;计划输出会显示Seq Scan on users、Filter: (age 25)、Rows Removed by Filter等关键字。对照README中关于投影、过滤、元组计数的描述我能清楚地看到每个执行节点实际上做了什么扫描节点从堆表读取整行元组放入TupleTableSlot过滤条件age 25在投影之前先求值不满足条件的元组直接被丢弃投影阶段只保留id和name两列生成新的窄元组向上传递顶层节点收集所有满足条件的元组交给上层输出模块。这个实验让我意识到EXPLAIN输出的不只是执行计划它其实是执行器内部状态在运行时留下的快照。如果你能理解README里的结构你就能把EXPLAIN的每一行输出和内部机制对应起来调优时自然更有底气。4.2 README中容易忽略的边界场景翻译过程中有几个容易被跳过的段落让我印象深刻它们恰恰是理解执行器边界能力的重点InitPlan与SubPlan的区别README花费不少篇幅解释InitPlan在查询启动时只执行一次的子计划和SubPlan每行可能重复执行的子计划的区别。前者典型场景是查询中的标量子查询后者典型场景是EXISTS子查询。这种区分对性能影响极大很多慢SQL其实就是把InitPlan用成了SubPlan。递归CTEWITH RECURSIVE的执行方式在参考文档中有专门段落它要求执行器能够保存“工作表”和“中间表”并在迭代中不断切换。没有这一步的说明很难理解为什么递归查询在某些深度下表现差异巨大。并行执行Parallel Query的引入让执行器新增了并行上下文和共享内存管理机制。README提到并行执行不仅仅是把节点跑在多个后台进程里还涉及元组分发、状态同步、结果合并等额外负担。这也解释了为什么PG的并行度设置不是越高越好。这些边界场景在日常业务里可能不常遇到但一旦遇到没有全局视野几乎无从排查。README的价值恰恰在于它把这些内容压缩在一个统一框架里让你能够快速定位问题所在。4.3 从README到源码的三大关键位置翻译完全文我把重点代码文件梳理成一张“阅读地图”对后来者应该也很实用文件作用关键词execMain.c执行器系统入口负责ExecutorStart、ExecutorRun等顶层函数Estate初始化、事务协调execProcnode.c节点调度中枢ExecInitNode和ExecProcNode的分发逻辑PlanState树构建、节点类型分发execExpr*.c表达式求值核心ExprState的构建与执行投影、过滤、JITnode*.c各具体节点的实现如nodeSeqscan.c、nodeAgg.c每个节点特有的执行逻辑我自己的阅读顺序是先读execMain.c的顶层流程再看execProcnode.c的节点分发逻辑然后跳到某个具体节点文件比如nodeSeqscan.c精读最后回到execExpr.c看表达式求值。这个路径基本就是沿着README的结构走下来的比漫无目的地翻文件高效得多。5. 这份译文适合怎么用给内核入门者的实操建议5.1 建议的阅读顺序如果你也想通过这份README来理解PG执行器我的建议是不要一上来就精读。第一遍快速通读不要纠结细节目标是建立“执行器大概由哪几块构成”的整体印象。第二遍读的时候对照一张EXPLAIN ANALYZE的实际输出把计划节点和文档里的节点类型对应起来。第三遍才需要逐段精读同时打开源码文件把文档中的每一个结构体和函数在代码里找到。三遍读下来执行器的大门基本就推开了。5.2 建立自己的术语表和代码笔记翻译过程中我维护了一个简单的术语表格格式大概是这样英文译法出处备注tuple元组全文通用物理行/内存行的统称slotslot全文通用保留原文result relation目标关系DML相关章节UPDATE/INSERT的目标表qual过滤条件节点相关章节qualification缩写projection投影表达式相关章节列裁剪与组合estateestate全文通用ExecutorState的惯用名这个小表不仅在翻译时帮了大忙后来做代码注释阅读时也一直在使用。建议每个想深入PG内核的人都可以建一份不断补充慢慢就会形成自己的概念体系。5.3 翻译之外我还想继续做的事这次翻译带来的连锁效应是我现在对PG的优化器如何生成Plan树产生了更大的兴趣。翻译完执行器README我发现下一步最值得读的是src/backend/optimizer/README把“计划怎么来的”接上“计划怎么跑的”整条链路就闭环了。与此同时我也在考虑写一篇结合PG 16代码对README进行逐段注释的长文。如果对你有帮助后续可以持续关注。最后再分享一个翻译之外的小经验如果你打算翻译类似的大项目文档不要一个人在云端闷头做最好把初稿放到一个你自己常逛的社区或版本仓库里哪怕只有一个读者他们的提问都会让你发现自己哪里其实没读懂。翻译的本质是理解而每一次被追问都是在逼自己再往前走一步。
RELATED

相关推荐

插件系统开发实战:从plugin.json到TypeScript SDK的加载机制与排查指南

插件系统开发实战:从plugin.json到TypeScript SDK的加载机制与排查指南

1. 从“plugins”这个词说起:它到底在解决什么问题如果你最近在折腾 Cursor、Codex CLI、Zcode CLI 这类工具,大概率会在某个时刻撞上plugins这个词。它可能出现在配置文件里,可能出现在启动日志里,也可能出现在某个报错信息里——…

📅 2026/10/5 15:44:14
深入解析插件体系:plugin.json清单、TypeScript SDK开发与CLI工作流

深入解析插件体系:plugin.json清单、TypeScript SDK开发与CLI工作流

1. 从“plugins”这个标题说起:它到底在指什么“plugins”这个词单独拎出来,信息量其实非常低。它可以是任何软件的插件目录、插件清单文件、插件加载器,也可以是一个插件市场的入口。但结合热搜词里反复出现的 Cursor、plugin.json、TypeScr…

📅 2026/10/5 15:44:14
NXP MCU CAN位时间配置详解:从公式到采样点与SJW

NXP MCU CAN位时间配置详解:从公式到采样点与SJW

1. 别急着填波特率:先认清位时间才是CAN通信的真正底牌 做CAN开发这么久,我见过最多的新手操作,是在NXP的MCU里打开配置工具,波特率一栏直接填500000,然后就不管了。等两块板子连起来,要么报文收不到&#…

📅 2026/10/5 15:39:14
MORE NEWS

更多资讯

📰

Prescan自动驾驶仿真:从安装配置到传感器建模联合仿真实战

如果你在做自动驾驶或者ADAS相关的开发,Prescan这个名字大概率已经绕不开了。它是一款环境感知级的仿真软件,核心价值是把摄像头、毫米波雷达、激光雷达这些传感器的测试场景搭起来,并输出可供下游算法使用的真值数据,再配合Simul…

📰

Prescan智能驾驶仿真实操指南:场景搭建、传感器配置与Simulink闭环验证

作为一个常年在智能驾驶仿真领域摸爬滚打的工程师,我接触Prescan已经好几年了。从最初被它的场景编辑能力吸引,到后来用它在项目里做传感器模型验证和算法闭环测试,再到带着团队里好几个新人用它跑通完整的仿真流程,可以说踩过的坑…

📰

Vue el-table多选实战:彻底解决分页、搜索下选中行丢失问题

1. 多选表格并不是加一列勾选框这么简单:先拆业务场景在 Vue 项目里,el-table 的多选功能几乎可以说是后台管理系统的"标配"了。不过每当我看到有人只花半分钟加一列type"selection"、再监听一个selection-change就宣布"多选搞…

📰

AI短剧创作三关:算力、叙事、交付的实战方法论

1. 这不是“用AI拍电影”,而是普通人闯入内容生产链的实战通关手册最近刷到一条新闻:某部全由AI生成的微短剧,片名《星尘回响》,在长三角三家独立影厅做了为期两周的点映,排片表上印着“AI导演:DeepReel-3.…

📰

AI Agent 生产级落地:七要素拆解与七个关键决策点

做了这么多年后端和系统设计,我一直有个固执的判断:AI Agent 真正难的不是“能跑通”,而是“能稳定地跑业务”。你可以两天搭出一个 demo,但要让它在生产环境里扛并发、不出错、可回溯、能停得住,背后是一整套工程问题…

📰

AI智能体批量进入V模型:从需求解析到测试生成的研发流水线实践

1. 从“单兵作战”到“批量列装”:AI智能体涌入V模型到底改变了什么如果你最近半年一直在关注AI智能体的落地进展,应该能明显感觉到一个拐点:前两年大家还在讨论“怎么让智能体跑通一个Demo”,而现在讨论的已经是“怎么让几十上百…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬