lyyyuna 的小花园

动静中之动, by

RSS

PostgreSQL 源码漫游(一):编译与进程架构

发表于 2026-03

前言

最近打算系统地读一读 PostgreSQL 的源码。PostgreSQL 大概是世界上最成熟的开源关系数据库了,代码库积累了三十多年,注释详尽、架构清晰,很适合作为学习数据库内核的材料。

这个系列我会从编译开始,逐步深入到各个子系统。第一篇先把全貌过一遍——怎么编译、有哪些进程、查询是怎么流转的。

从源码编译

PostgreSQL 的源码可以从 GitHub 镜像获取:

git clone https://github.com/postgres/postgres.git
cd postgres

PostgreSQL 支持两套构建系统:传统的 Autoconf/Make 和较新的 Meson。日常开发用 Autoconf 就好:

# 1. 配置(检测平台、依赖、生成 Makefile)
./configure --prefix=$HOME/pgsql

# 2. 编译
make -j$(nproc)

# 3. 安装
make install

编译依赖不多,主要是:

./configure 会在各个子目录生成 Makefile,核心的全局配置在 src/Makefile.global.in。整个源码树大致是这样:

postgres/
├── src/
│   ├── backend/      # 服务端核心(这是重头戏)
│   ├── bin/          # 客户端工具:psql, pg_dump, initdb 等
│   ├── include/      # 头文件
│   ├── interfaces/   # 客户端库 libpq
│   ├── pl/           # 过程语言:PL/pgSQL, PL/Python 等
│   ├── common/       # 前后端共用代码
│   ├── port/         # 平台兼容层
│   ├── test/         # 回归测试
│   └── tools/        # 开发辅助工具
├── contrib/          # 官方扩展(pg_stat_statements 等)
└── doc/              # 文档

后续文章基本都会泡在 src/backend/ 里。

多进程架构

PostgreSQL 采用的是经典的 process-per-connection 模型——每个客户端连接由一个独立的操作系统进程服务。这和 MySQL 的多线程模型是不同路线的选择。

整个系统的进程可以分为三类:

1. Postmaster(进程管理器)

Postmaster 是 PostgreSQL 启动后的主进程,代码在 src/backend/postmaster/postmaster.c。它做的事情很直接:

Postmaster 自己不访问共享内存,这是有意为之——万一某个子进程把共享内存搞坏了,Postmaster 不会被连带卡死,还能做 crash recovery。

程序的入口在 src/backend/main/main.c,它根据命令行参数决定走哪条路:

2. 后台辅助进程

Postmaster 启动后会依次拉起一组辅助进程,各司其职:

进程 代码位置 职责
Startup access/xlogrecovery.c 启动时做 crash recovery,回放 WAL
Background Writer postmaster/bgwriter.c 后台把脏页刷到磁盘,减轻 checkpoint 压力
Checkpointer postmaster/checkpointer.c 定期做 checkpoint,确保数据持久化
WAL Writer postmaster/walwriter.c 周期性把 WAL 缓冲区刷盘
Autovacuum Launcher postmaster/autovacuum.c 自动触发 VACUUM 和 ANALYZE
Archiver postmaster/pgarch.c 把完成的 WAL 段拷贝到归档目录
WAL Summarizer postmaster/walsummarizer.c 汇总 WAL 信息,用于增量备份
Syslogger postmaster/syslogger.c 收集所有进程的 stderr,写日志文件

这些进程启动有先后顺序。Postmaster 状态机的流转大致是:

  1. PM_STARTUP:先拉起 Startup 进程做恢复,同时启动 Checkpointer 和 Background Writer 辅助恢复
  2. PM_RUN:恢复完成,进入正常运行状态,启动 WAL Writer、Autovacuum Launcher、Archiver 等
  3. 开始接受客户端连接

3. Backend(用户后端进程)

每个客户端连接对应一个 Backend 进程。Postmaster 收到连接请求后 fork 子进程,子进程先走认证流程(BackendMain()),通过后进入查询处理主循环(PostgresMain()),代码在 src/backend/tcop/postgres.c

这个主循环做的事情就是:读命令 → 执行 → 返回结果 → 读下一个命令,直到客户端断开。

ps 看一个运行中的 PostgreSQL 实例,大概是这样:

postgres: postmaster
postgres: checkpointer
postgres: background writer
postgres: walwriter
postgres: autovacuum launcher
postgres: logical replication launcher
postgres: user1 mydb [local] idle
postgres: user2 mydb 192.168.1.5(43210) SELECT

最后两行就是 Backend 进程,能看到是谁连的、连的哪个库、当前在干什么。

查询处理流水线

一条 SQL 从客户端发出到结果返回,在 Backend 进程内部经过五个阶段。这条流水线是 PostgreSQL 最核心的骨架:

SQL 字符串
    │
    ▼
┌──────────┐
│  Parser  │  词法分析 + 语法分析 + 语义分析
└────┬─────┘
     │ Parse Tree
     ▼
┌──────────┐
│ Rewriter │  规则重写(视图展开等)
└────┬─────┘
     │ Query Tree
     ▼
┌──────────┐
│ Planner  │  生成执行路径,选择代价最低的方案
└────┬─────┘
     │ Plan Tree
     ▼
┌──────────┐
│ Executor │  按需拉取模型,逐行产出结果
└────┬─────┘
     │ Result Rows
     ▼
  返回客户端

分别来看。

Parser(解析器)

代码在 src/backend/parser/。分两步:

  1. 词法 + 语法分析scan.l(Flex)做词法分析,gram.y(Bison)做语法分析,产出原始语法树(Raw Parse Tree)
  2. 语义分析analyze.c 负责把名字解析成实际的数据库对象——表名查 catalog、列名做类型检查,产出 Query 结构体

Rewriter(规则重写)

代码在 src/backend/rewrite/。如果表上定义了 RULE(比如视图本质上就是一个 SELECT 规则),在这一步展开。大部分普通查询走过这步不会有变化。

Planner/Optimizer(计划器/优化器)

代码在 src/backend/optimizer/,这是最复杂的部分。它要回答的问题是:用什么方式访问表、用什么算法做 JOIN、按什么顺序连接多张表。

工作流程:

  1. 路径生成optimizer/path/):对每张表列出所有可能的访问方式(顺序扫描、各种索引扫描),对每种 JOIN 列出可能的算法(Nested Loop、Hash Join、Merge Join)
  2. JOIN 排列:用动态规划找最优的连接顺序。如果表太多(默认阈值 12 张),切换到 GEQO(遗传算法)避免指数爆炸
  3. 计划生成optimizer/plan/):选出代价最低的路径,转化为 Plan 树

PostgreSQL 的优化器是基于代价的(cost-based),会估算每种方案的 I/O 和 CPU 开销。

Executor(执行器)

代码在 src/backend/executor/。拿到 Plan 树后开始实际执行。

执行器采用 demand-pull(按需拉取)模型——父节点调用 ExecProcNode(子节点) 获取下一行,子节点要么返回一行数据,要么返回 NULL 表示没了。这种设计让流水线式处理成为可能,不需要把中间结果全部物化。

举个例子,一个 Merge Join 的执行树:

MergeJoin
├── Sort
│   └── SeqScan (table_a)
└── Sort
    └── IndexScan (table_b)

MergeJoin 交替从左右两个 Sort 节点拉数据,Sort 节点先把下层的全部数据缓存并排序,然后逐行返回。

存储层

执行器下面还有一层存储层,分布在 src/backend/storage/src/backend/access/

小结

PostgreSQL 的架构可以归纳为几个关键设计决策:

  1. Process-per-connection:每个连接一个进程,通过共享内存通信。简单可靠,一个 Backend 崩溃不会拖垮整个系统
  2. Postmaster 不碰共享内存:保证了主进程的健壮性
  3. 五阶段查询流水线:Parser → Rewriter → Planner → Executor → Storage,层次分明,每层有清晰的输入输出
  4. Demand-pull 执行模型:让流水线式处理成为可能,内存友好
  5. WAL + Checkpoint:经典的预写日志方案,兼顾性能和可靠性

后面的文章会逐个深入这些子系统。下一篇可能从 Parser 开始,看看一条 SQL 是怎么变成语法树的。

lyyyuna 沪ICP备2025110782号-1