3. 跟踪一次简单查询的执行流程
任务说明
本案例以PostgreSQL 16.x源码为基础,跟踪一条最简单的查询SELECT 1;从客户端发送到服务端返回结果的完整执行链路。目标是在源码层面定位这条查询所经过的核心函数,理解查询执行的主干流程,并绘制出对应的调用流程图。案例不涉及优化器复杂路径、并行执行或索引扫描等扩展内容,聚焦于最基础的执行主线。
核心执行链路定位
PostgreSQL采用经典的进程模型:每个客户端连接对应一个后端进程(backend process)。查询从客户端到达后,依次经过词法/语法解析、分析、重写、计划、执行、结果返回几个阶段。对于SELECT 1;这类常量查询,优化器会生成一个Result节点,执行器直接计算并返回结果,不涉及表扫描。
关键函数入口位于src/backend/tcop/postgres.c中的PostgresMain(),这是后端进程的主循环。核心调用链如下:
PostgresMain()→exec_simple_query():处理单条简单查询;exec_simple_query()→pg_parse_query():调用词法/语法解析器,生成原始解析树;- →
pg_analyze_and_rewrite():对解析树做语义分析(生成查询树Query)并应用规则重写; - →
pg_plan_queries():调用查询优化器,生成执行计划(PlannedStmt); - →
PortalRun():进入执行阶段,通过执行器运行计划; - →
PortalRunSelect()→ExecutorRun():执行器开始运行; - →
standard_ExecutorRun()→ExecutePlan():驱动执行节点树; - →
ExecResult():执行Result节点,计算常量表达式并返回一行结果; - → 结果通过
SendQueryResults()返回客户端。
关键源码位置与函数职责
| 函数 | 文件位置 | 职责 |
|---|---|---|
PostgresMain() | src/backend/tcop/postgres.c | 后端进程主循环,接收客户端消息并分发 |
exec_simple_query() | src/backend/tcop/postgres.c | 处理无绑定参数的简单查询 |
pg_parse_query() | src/backend/tcop/postgres.c | 调用解析器生成RawStmt列表 |
pg_analyze_and_rewrite() | src/backend/tcop/postgres.c | 语义分析+规则重写,生成Query |
pg_plan_queries() | src/backend/tcop/postgres.c | 生成PlannedStmt执行计划 |
PortalRun() | src/backend/tcop/pquery.c | 运行Portal,进入执行阶段 |
ExecutorRun() | src/backend/executor/execMain.c | 执行器统一入口 |
ExecutePlan() | src/backend/executor/execMain.c | 驱动节点树,逐行获取结果 |
ExecResult() | src/backend/executor/nodeResult.c | 执行Result节点,计算目标列 |
验证方法
要验证上述调用链,推荐使用gdb附加到后端进程并设置断点。操作步骤如下:
- 编译PostgreSQL时保留调试符号(
./configure --enable-debug); - 启动数据库后,找到后端进程PID:
SELECT pg_backend_pid();; - 使用gdb附加:
gdb -p PID; - 在关键函数设置断点:
break exec_simple_query、break pg_parse_query、break pg_analyze_and_rewrite、break pg_plan_queries、break PortalRun、break ExecResult; - 在另一个终端执行
psql -c "SELECT 1;"; - 观察gdb中断点命中的顺序,应依次经过上述函数。
判定标准:若断点按exec_simple_query → pg_parse_query → pg_analyze_and_rewrite → pg_plan_queries → PortalRun → ExecResult的顺序依次命中,且最终客户端收到结果1,则验证通过。
执行流程时序图
补充说明
上述链路中,PortalRun()内部还会经过PortalRunSelect()、ExecutorRun()、standard_ExecutorRun()、ExecutePlan()等函数,最终才到达ExecResult()。这些中间函数位于src/backend/tcop/pquery.c和src/backend/executor/execMain.c中,属于执行器的标准调用路径。对于SELECT 1;,优化器生成的计划树只有一个Result节点,因此执行器直接调用ExecResult()完成计算,无需访问存储引擎。
若希望观察更详细的函数调用层次,可在gdb中使用bt命令查看完整调用栈,或在ExecutorRun()处设置断点后单步跟踪。本案例给出的链路已覆盖从客户端到执行节点的完整主干,足以支撑对PostgreSQL查询执行流程的源码级理解。