PostgreSQL 源码剖析总览
0. 摘要
项目目标
PostgreSQL 是一个先进的对象关系型数据库管理系统(ORDBMS),实现了扩展的 SQL 标准子集,支持 ACID 事务、多版本并发控制(MVCC)、复杂查询、触发器、存储过程、外键约束等企业级特性。
核心能力边界
- SQL 查询解析、优化与执行
- 基于堆表的存储引擎,支持多种索引类型(B-Tree、Hash、GiST、GIN、SP-GiST、BRIN)
- MVCC 事务隔离机制,支持四种标准隔离级别
- WAL(Write-Ahead Logging)日志系统,保证数据持久性与崩溃恢复
- 流复制与逻辑复制,支持高可用与数据同步
- 扩展机制,允许加载自定义类型、函数、索引方法等
非目标
- 非列存储数据库(虽有扩展支持)
- 非内存数据库(主要面向持久化存储)
- 非自动分片(需借助外部工具如 Citus)
运行环境
- 语言:C(C99 标准)
- 平台:Linux、Unix、Windows、macOS
- 依赖:标准 C 库、可选依赖 OpenSSL/GSSAPI(安全)、ICU(国际化)、LLVM(JIT 编译)
- 部署形态:单机多进程架构,主进程(postmaster)管理多个后端进程(backend)
系统架构类型
- 进程模型:多进程架构,每个客户端连接对应一个独立的后端进程
- 通信方式:共享内存 + 信号量 + Socket
- 存储模式:基于页的堆表存储,默认页大小 8KB
1. 整体架构图
flowchart TB
Client[客户端 psql/应用] -->|libpq 协议| Postmaster[Postmaster 主进程]
Postmaster -->|fork| Backend[Backend 后端进程]
Postmaster -->|管理| BgWriter[Background Writer]
Postmaster -->|管理| WALWriter[WAL Writer]
Postmaster -->|管理| Checkpointer[Checkpointer]
Postmaster -->|管理| AutoVacuum[Autovacuum Launcher]
Postmaster -->|管理| StatsCollector[Stats Collector]
Postmaster -->|管理| WalSender[WAL Sender]
Postmaster -->|管理| WalReceiver[WAL Receiver]
Backend -->|1. 解析| Parser[Parser 解析器]
Parser -->|2. 分析| Analyzer[Analyzer 语义分析]
Analyzer -->|3. 重写| Rewriter[Rewriter 查询重写]
Rewriter -->|4. 优化| Planner[Planner/Optimizer 优化器]
Planner -->|5. 执行| Executor[Executor 执行器]
Executor -->|访问| AccessMethods[Access Methods 访问方法]
AccessMethods -->|堆表| HeapAM[Heap AM]
AccessMethods -->|索引| IndexAM[Index AM<br/>B-Tree/Hash/GiST/GIN]
HeapAM -->|读写| BufferManager[Buffer Manager 缓冲区管理]
IndexAM -->|读写| BufferManager
BufferManager -->|页面缓存| SharedBuffers[(Shared Buffers<br/>共享内存)]
BufferManager -->|写盘| Storage[(磁盘存储<br/>数据文件)]
Executor -->|事务| TransactionMgr[Transaction Manager]
TransactionMgr -->|MVCC| CLOG[(CLOG<br/>事务状态)]
TransactionMgr -->|生成| WAL[(WAL Log<br/>预写日志)]
WAL -->|写入| WALWriter
WAL -->|复制| WalSender
WalSender -->|流| WalReceiver
Executor -->|元数据| Catalog[System Catalog<br/>系统表]
Catalog -->|缓存| SysCache[SysCache<br/>系统表缓存]
BgWriter -->|刷脏页| SharedBuffers
Checkpointer -->|检查点| Storage
AutoVacuum -->|清理| Storage
图解与要点
组件职责
1. Postmaster(主进程)
- 职责:监听端口(默认 5432),接受客户端连接,为每个连接 fork 新的后端进程
- 生命周期:数据库启动到关闭全程运行
- 子进程管理:启动和监控各类辅助进程(WAL Writer、Background Writer 等)
2. Backend(后端进程)
- 职责:处理单个客户端的所有 SQL 请求
- 隔离性:每个后端进程独立地址空间,通过共享内存共享数据
- 生命周期:客户端连接建立到断开
3. Parser(解析器)
- 输入:SQL 文本
- 输出:解析树(Parse Tree)
- 核心任务:词法分析(Lexer)、语法分析(Bison 生成的语法解析器)
4. Analyzer(语义分析器)
- 输入:解析树
- 输出:查询树(Query Tree)
- 核心任务:名称解析(表名、列名)、类型检查、权限检查
5. Rewriter(重写器)
- 输入:查询树
- 输出:重写后的查询树
- 核心任务:视图展开、规则系统应用、行级安全策略应用
6. Planner/Optimizer(优化器)
- 输入:查询树
- 输出:执行计划(Plan Tree)
- 核心任务:路径生成、代价估算、最优计划选择
7. Executor(执行器)
- 输入:执行计划
- 输出:结果元组(Tuple)
- 核心任务:按计划树递归执行,调用访问方法读取数据
8. Access Methods(访问方法)
- 职责:提供统一的接口访问表和索引
- 类型:
- Heap AM:堆表访问方法(PostgreSQL 默认表存储)
- Index AM:索引访问方法(B-Tree、Hash、GiST、GIN、SP-GiST、BRIN)
9. Buffer Manager(缓冲区管理器)
- 职责:管理共享内存中的页面缓存(Shared Buffers)
- 策略:时钟扫描算法(Clock-Sweep)替换页面
- 同步:锁保护(Buffer Content Lock、Buffer IO Lock)
10. Transaction Manager(事务管理器)
- 职责:MVCC 实现、事务状态跟踪、快照管理
- 核心数据结构:
- CLOG(Commit Log):记录每个事务的提交/回滚状态
- PGXACT:当前运行事务的轻量信息
- Snapshot:事务可见性快照
11. WAL(Write-Ahead Log)
- 职责:保证数据持久性与崩溃恢复
- 机制:所有修改先写 WAL,再写数据页
- 组件:
- WAL Writer:异步刷新 WAL 缓冲区到磁盘
- WAL Sender:将 WAL 流式传输到备库
12. Background Writer
- 职责:周期性将共享缓冲区的脏页写入磁盘
- 目的:平滑 I/O 峰值,减少检查点时的批量写入
13. Checkpointer
- 职责:执行检查点操作,确保所有脏页落盘
- 策略:基于时间或 WAL 量触发检查点
14. Autovacuum
- 职责:自动清理死元组、更新统计信息、防止事务 ID 回卷
- 触发:基于表的修改量阈值
15. System Catalog
- 职责:存储数据库元数据(表定义、索引、类型、函数等)
- 实现:系统表(pg_class、pg_attribute、pg_proc 等)
- 缓存:SysCache 减少系统表访问开销
数据流与控制流
SQL 执行流程(同步路径)
Client SQL → Postmaster → Backend → Parser → Analyzer → Rewriter → Planner → Executor → Access Methods → Buffer Manager → Disk/SharedMem → Backend → Client
数据修改流程(写入路径)
Executor (INSERT/UPDATE/DELETE)
→ Heap AM 修改页面
→ WAL Insert Record
→ Buffer Manager 标记脏页
→ WAL Writer 刷 WAL
→ Background Writer/Checkpointer 刷数据页
事务提交流程
COMMIT 命令
→ Transaction Manager 记录 CLOG
→ WAL Flush(同步刷盘)
→ 返回客户端成功
跨进程通信
共享内存段
- Shared Buffers:数据页缓存
- WAL Buffers:WAL 日志缓冲
- Lock Tables:锁管理表
- CLOG Buffers:事务状态缓存
- Proc Array:所有后端进程信息
信号机制
- SIGTERM:优雅关闭
- SIGUSR1:自定义信号(如通知 Checkpointer)
- SIGUSR2:触发日志切换
高可用与扩展性
流复制(Streaming Replication)
- 主库:WAL Sender 进程将 WAL 实时发送到备库
- 备库:WAL Receiver 接收 WAL,Startup 进程应用 WAL
- 同步级别:异步、同步、法定人数同步
逻辑复制(Logical Replication)
- 发布/订阅模型
- 基于逻辑解码(Logical Decoding)从 WAL 提取变更
- 支持表级别、选择性复制
扩展性
- 纵向扩展:增加内存(Shared Buffers)、CPU(并行查询)
- 横向扩展:需外部工具(Citus、Postgres-XL)
- 连接池:pgBouncer、PgPool-II 减少进程开销
状态管理位置
进程内状态
- 每个后端进程的私有内存(Local Memory)
- 事务上下文、执行上下文、会话变量
共享状态
- Shared Buffers(所有进程共享)
- Lock Manager(全局锁表)
- Proc Array(所有活跃事务信息)
资源上界与性能关键点
内存限制
shared_buffers:共享缓冲区大小(推荐系统内存的 25%)work_mem:每个查询操作(排序、哈希)的内存限制maintenance_work_mem:维护操作(VACUUM、CREATE INDEX)内存
连接限制
max_connections:最大并发连接数(每个连接一个进程,内存开销大)
磁盘 I/O 热点
- WAL 磁盘:写入密集,建议 SSD 或独立磁盘
- 数据文件:随机读写,索引扫描性能依赖磁盘 IOPS
- 临时文件:大查询(如排序、哈希连接)超出
work_mem时写临时文件
CPU 瓶颈
- 复杂查询优化(Planner)
- 表达式计算(Executor)
- 可通过 JIT 编译加速(LLVM)
2. 全局时序图(主要业务闭环)
场景:SELECT 查询执行流程
sequenceDiagram
autonumber
participant C as 客户端
participant PM as Postmaster
participant BE as Backend进程
participant P as Parser
participant A as Analyzer
participant RW as Rewriter
participant PL as Planner
participant EX as Executor
participant AM as AccessMethods
participant BM as BufferManager
participant SB as SharedBuffers
participant DISK as 磁盘
C->>PM: 连接请求(TCP 5432)
PM->>BE: fork 新后端进程
BE-->>C: 连接成功
C->>BE: SELECT * FROM users WHERE id = 1
BE->>P: 词法/语法分析
P-->>BE: ParseTree(解析树)
BE->>A: 语义分析
A->>A: 查找系统表(pg_class, pg_attribute)
A-->>BE: QueryTree(查询树)
BE->>RW: 应用规则/视图展开
RW-->>BE: 重写后的QueryTree
BE->>PL: 生成执行计划
PL->>PL: 路径生成(SeqScan/IndexScan)
PL->>PL: 代价估算(CPU/IO)
PL-->>BE: PlanTree(执行计划)
BE->>EX: 执行计划
EX->>AM: 访问表(Heap Scan 或 Index Scan)
AM->>BM: 请求页面(Page Number)
alt 页面在缓冲区
BM->>SB: 命中缓存
SB-->>BM: 返回页面
else 页面不在缓冲区
BM->>DISK: 读取页面
DISK-->>BM: 返回页面
BM->>SB: 加载到缓冲区
end
BM-->>AM: 页面数据
AM->>AM: 扫描页面,过滤元组
AM-->>EX: 返回元组
EX->>EX: 检查 MVCC 可见性
EX-->>BE: 结果集
BE-->>C: 返回结果
图解与要点
入口阶段(步骤 1-3)
- Postmaster 监听端口,收到连接请求后 fork 新的后端进程
- Backend 进程通过 libpq 协议接收 SQL 命令
- 每个连接独立进程,隔离性强但内存开销大
解析阶段(步骤 4-6)
- Parser 使用 Flex(词法分析器)和 Bison(语法分析器)生成解析树
- 解析树仅验证语法正确性,不检查语义(如表是否存在)
- 输出:抽象语法树(AST),表示为
SelectStmt等节点类型
语义分析阶段(步骤 7-9)
- Analyzer 查询系统表(pg_class、pg_attribute、pg_type)验证对象存在性
- 进行类型推导与转换(如字符串常量 ‘1’ 转为整数)
- 检查访问权限(基于 pg_authid 和 ACL)
- 输出:查询树(Query),包含 RTE(Range Table Entry)列表
重写阶段(步骤 10-11)
- 视图展开:将视图引用替换为其定义的 SELECT
- 规则应用:执行 ON SELECT DO INSTEAD 规则
- 行级安全策略(RLS):添加额外的过滤条件
- 输出:重写后的查询树
优化阶段(步骤 12-15)
- Planner 生成多种执行路径(顺序扫描、索引扫描、索引唯一扫描等)
- 代价估算基于统计信息(pg_statistic)和配置参数(random_page_cost 等)
- 选择总代价最低的路径
- 输出:执行计划树(Plan),节点类型如
SeqScan、IndexScan、NestLoop等
执行阶段(步骤 16-26)
- Executor 递归执行计划树,按火山模型(Volcano Model)逐元组处理
- 调用访问方法(Heap AM 或 Index AM)获取元组
- Buffer Manager 管理页面缓存:
- 命中:直接返回页面指针
- 未命中:从磁盘读取,选择牺牲页(时钟算法),加载新页
- MVCC 可见性检查:根据事务快照判断元组是否对当前事务可见
- 输出:结果元组流,发送回客户端
关键约束点
1. 幂等性
- SELECT 查询天然幂等,多次执行不改变数据库状态
- 但在读已提交(Read Committed)隔离级别下,多次执行可能看到不同结果(因其他事务的提交)
2. 锁与并发
- SELECT 在 MVCC 下不阻塞 UPDATE/DELETE(无需读锁)
- 仅在系统表查询时短暂持有系统表的 AccessShareLock
- 长事务会导致快照过旧,影响 VACUUM 清理死元组
3. 超时
statement_timeout:单条 SQL 执行时间上限lock_timeout:等待锁的时间上限idle_in_transaction_session_timeout:事务空闲超时
4. 资源上界
- 单次查询内存:
work_mem(排序、哈希操作) - 结果集过大:客户端可能内存溢出(建议游标分批获取)
- Shared Buffers 竞争:高并发下缓冲区锁成为瓶颈
5. 回退策略
- 查询执行失败(如语法错误、权限不足):返回错误,事务可继续
- 内部错误(如段错误):后端进程崩溃,Postmaster 重启所有后端进程
- 死锁检测:超过
deadlock_timeout后触发死锁检测,牺牲一个事务
6. 重试点
- 客户端应重试的错误:
40001(serialization_failure):可串行化隔离级别冲突40P01(deadlock_detected):死锁57P03(cannot_connect_now):服务器启动中
- 不应重试的错误:
42P01(undefined_table):表不存在42501(insufficient_privilege):权限不足
场景:INSERT 插入数据流程
sequenceDiagram
autonumber
participant C as 客户端
participant BE as Backend进程
participant P as Parser
participant PL as Planner
participant EX as Executor
participant HA as HeapAM
participant WAL as WAL日志
participant BM as BufferManager
participant TM as TransactionMgr
participant CLOG as CLOG
C->>BE: BEGIN; INSERT INTO users VALUES (1, 'Alice')
BE->>P: 解析 BEGIN
P-->>BE: TransactionStmt
BE->>TM: 开始事务
TM->>TM: 分配事务ID(XID)
TM-->>BE: XID = 12345
BE->>P: 解析 INSERT
P-->>BE: InsertStmt
BE->>PL: 生成计划
PL-->>BE: ModifyTable(Insert) Plan
BE->>EX: 执行 INSERT
EX->>HA: heap_insert()
HA->>BM: 获取表的最后一页或新页
BM-->>HA: 页面指针
HA->>HA: 在页面中分配空间
HA->>HA: 写入元组(带 XID=12345)
HA->>WAL: XLogInsert(INSERT_RECORD)
WAL->>WAL: 记录 WAL(LSN=100)
HA->>BM: 标记页面为脏页
BM->>BM: 记录页面 LSN=100
HA-->>EX: 插入成功
EX-->>BE: 返回成功
BE-->>C: INSERT 0 1
C->>BE: COMMIT
BE->>TM: 提交事务
TM->>CLOG: 标记 XID=12345 为已提交
TM->>WAL: XLogInsert(COMMIT_RECORD)
WAL->>WAL: 同步刷盘(fsync)
WAL-->>TM: 刷盘完成
TM-->>BE: 提交成功
BE-->>C: COMMIT
图解与要点
事务开始(步骤 1-5)
BEGIN启动事务,Transaction Manager 从共享内存的ProcArray分配事务 ID(XID)- XID 单调递增,全局唯一(32 位整数,有回卷风险,需 VACUUM)
- 事务状态初始为
IN_PROGRESS
插入执行(步骤 6-16)
- Planner 生成
ModifyTable计划节点,操作类型为CMD_INSERT - Executor 调用
heap_insert()函数:- 选择目标页面:优先使用 FSM(Free Space Map)查找有空闲空间的页
- 在页面中写入元组:
- 元组头(HeapTupleHeader)包含
t_xmin = 12345(插入事务 ID) t_xmax = 0(未被删除)t_ctid(指向自身,元组标识符)
- 元组头(HeapTupleHeader)包含
- 生成 WAL 记录:
- 记录类型:
XLOG_HEAP_INSERT - 包含页面修改前镜像(FPI,首次修改时)或仅记录增量
- 记录类型:
- 标记页面为脏页,记录页面的 LSN(Log Sequence Number)
WAL 写入机制
- WAL 缓冲区(WAL Buffers)在共享内存中,大小由
wal_buffers配置 - 写入时机:
- 事务提交时强制刷盘
- WAL 缓冲区满时
- WAL Writer 进程周期性刷新(
wal_writer_delay)
- 保证:WAL 落盘后数据修改才可见(Write-Ahead)
事务提交(步骤 17-23)
COMMIT命令触发提交流程:- 在 CLOG 中标记 XID=12345 为
COMMITTED - 生成
XLOG_XACT_COMMITWAL 记录 - 同步刷新 WAL 到磁盘(
fsync或fdatasync) - 释放事务持有的锁
- 从
ProcArray移除事务信息
- 在 CLOG 中标记 XID=12345 为
关键约束点
1. 幂等性
- INSERT 非幂等:多次执行插入多行
- 解决方案:
- 唯一约束/主键:重复插入失败
INSERT ... ON CONFLICT DO NOTHING:UPSERT 语义
2. 锁机制
- 表级锁:
RowExclusiveLock(允许并发 SELECT/INSERT/UPDATE/DELETE) - 行级锁:不显式加锁,通过 MVCC 实现(t_xmin/t_xmax)
- 索引页锁:插入索引条目时短暂持有页锁
3. 回退策略
- 唯一约束冲突:返回错误
23505(unique_violation),事务可继续(需显式 ROLLBACK 或继续操作) - 磁盘满:WAL 写入失败,数据库进入只读模式(需清理 WAL)
- 崩溃恢复:启动时重放 WAL,恢复到一致状态
4. 性能要点
- 批量插入:
COPY命令比多次 INSERT 快(减少 WAL 记录、优化缓冲区使用) - 关闭 WAL:
wal_level=minimal(仅支持崩溃恢复,不支持复制) - 禁用 fsync:
fsync=off(性能提升但不保证持久性,仅测试环境)
场景:UPDATE 更新数据流程(MVCC 机制)
sequenceDiagram
autonumber
participant C as 客户端
participant BE as Backend进程
participant EX as Executor
participant HA as HeapAM
participant BM as BufferManager
participant TM as TransactionMgr
participant WAL as WAL日志
C->>BE: BEGIN; UPDATE users SET name='Bob' WHERE id=1
BE->>TM: 开始事务(XID=12346)
TM->>TM: 创建快照(xmin=12345, xmax=12346)
TM-->>BE: 事务开始
BE->>EX: 执行 UPDATE
EX->>HA: 扫描表,查找 id=1 的元组
HA->>BM: 读取页面
BM-->>HA: 返回页面
HA->>HA: 定位旧元组(tuple_old)
HA->>TM: 检查可见性(tuple_old.t_xmin=12345)
TM->>TM: 12345 < xmax,且已提交 → 可见
TM-->>HA: 可见
HA->>HA: 标记旧元组(tuple_old.t_xmax=12346)
HA->>HA: 在同页或新页插入新元组(tuple_new)
HA->>HA: tuple_new.t_xmin=12346, t_xmax=0
HA->>HA: 更新元组链(tuple_old.t_ctid → tuple_new)
HA->>WAL: XLogInsert(UPDATE_RECORD)
WAL->>WAL: 记录旧元组 t_xmax 修改 + 新元组插入
HA->>BM: 标记页面为脏页
HA-->>EX: 更新成功
EX-->>BE: 返回成功
BE-->>C: UPDATE 1
C->>BE: COMMIT
BE->>TM: 提交事务(XID=12346)
TM->>TM: CLOG 标记 12346 为 COMMITTED
TM->>WAL: 刷 WAL 到磁盘
TM-->>BE: 提交完成
BE-->>C: COMMIT
图解与要点
MVCC 核心机制
PostgreSQL 的 UPDATE 实现为 “DELETE + INSERT” 的组合:
- 旧元组不立即删除,而是标记
t_xmax为当前事务 ID - 新元组插入到同页或新页,
t_xmin为当前事务 ID - 通过元组链(
t_ctid)关联旧版本和新版本
可见性规则
事务快照包含:
xmin:快照创建时最小的活跃事务 IDxmax:下一个未分配的事务 IDxip[]:快照创建时所有活跃事务 ID 列表
元组对事务 T 可见的条件:
t_xmin < T.xmin且t_xmin已提交 且(t_xmax为空 或t_xmax未提交 或t_xmax > T.xmax)
HOT 更新优化(Heap-Only Tuple)
当 UPDATE 不修改索引列时:
- 新元组放在同一页(如有空间)
- 索引无需更新(减少索引维护开销)
t_ctid指向同页的新元组,索引仍指向旧元组
关键约束点
1. 并发控制
- 多个事务同时 UPDATE 同一行:
- 第一个事务标记
t_xmax,其他事务等待 - 等待通过元组的
t_xmax检查:如果该事务未提交,等待其结束 - 如果第一个事务提交,后续事务基于新版本重试
- 如果第一个事务回滚,后续事务基于旧版本继续
- 第一个事务标记
2. 锁升级
- 行级锁通过元组头实现(无需锁表)
- 锁模式:
FOR UPDATE:排他锁,阻塞其他 UPDATE/DELETE/SELECT FOR UPDATEFOR SHARE:共享锁,允许其他 FOR SHARE,阻塞 UPDATE/DELETE
3. 死锁检测
- 场景:事务 A 等待事务 B 持有的行锁,事务 B 等待事务 A 持有的行锁
- 检测:超过
deadlock_timeout(默认 1 秒)后触发死锁检测 - 解决:牺牲一个事务(通常是后到达的),回滚并返回错误
40P01
4. VACUUM 必要性
- UPDATE 产生死元组(旧版本),占用磁盘空间
- VACUUM 清理不再被任何事务可见的死元组
- Autovacuum 根据表的修改量自动触发
5. 性能要点
- 频繁 UPDATE 的表:死元组积累快,需调高 Autovacuum 频率
- 索引列 UPDATE:触发索引更新,开销大
fillfactor参数:预留页面空间用于 HOT 更新(如fillfactor=70预留 30%)
3. 模块边界与交互矩阵
模块清单与目录映射
| 序号 | 模块名称 | 源码目录 | 职责概述 |
|---|---|---|---|
| 01 | Parser(解析器) | src/backend/parser |
将 SQL 文本解析为抽象语法树(Parse Tree) |
| 02 | Analyzer(语义分析器) | src/backend/parser(analyze.c) |
语义检查、名称解析、类型推导 |
| 03 | Rewriter(重写器) | src/backend/rewrite |
视图展开、规则应用、RLS 策略应用 |
| 04 | Planner/Optimizer(优化器) | src/backend/optimizer |
生成执行计划、代价估算、路径选择 |
| 05 | Executor(执行器) | src/backend/executor |
执行计划树,返回结果元组 |
| 06 | Access Methods(访问方法) | src/backend/access |
堆表与索引的统一访问接口 |
| 07 | Heap AM(堆表访问) | src/backend/access/heap |
基于堆的表存储实现 |
| 08 | Index AM(索引访问) | src/backend/access/{nbtree,hash,gin,gist,spgist,brin} |
B-Tree、Hash、GIN、GiST、SP-GiST、BRIN 索引 |
| 09 | Buffer Manager(缓冲管理) | src/backend/storage/buffer |
共享缓冲区管理、页面替换 |
| 10 | Transaction Manager(事务管理) | src/backend/access/transam |
MVCC、事务状态、快照管理 |
| 11 | CLOG(事务状态日志) | src/backend/access/transam/clog.c |
记录事务提交/回滚状态 |
| 12 | WAL(预写日志) | src/backend/access/transam/xlog.c |
日志写入、崩溃恢复、复制 |
| 13 | Lock Manager(锁管理) | src/backend/storage/lmgr |
表锁、行锁、死锁检测 |
| 14 | Catalog(系统表) | src/backend/catalog |
元数据管理(表、索引、类型定义) |
| 15 | SysCache(系统表缓存) | src/backend/utils/cache |
系统表查询缓存,减少重复查询 |
| 16 | Commands(DDL/DML 命令) | src/backend/commands |
DDL 命令实现(CREATE、ALTER、DROP) |
| 17 | Replication(复制) | src/backend/replication |
流复制、逻辑复制 |
| 18 | Postmaster(进程管理) | src/backend/postmaster |
主进程、连接管理、子进程监控 |
| 19 | libpq Protocol(协议层) | src/backend/libpq |
前后端通信协议、认证 |
| 20 | Utils(工具函数) | src/backend/utils |
内存管理、排序、哈希表、错误处理 |
模块交互矩阵
| 调用方 → 被调方 | Catalog | Buffer Mgr | Heap AM | Index AM | Transaction Mgr | WAL | Lock Mgr | 同步/异步 | 错误语义 | 一致性要求 |
|---|---|---|---|---|---|---|---|---|---|---|
| Parser | ✓ | - | - | - | - | - | - | 同步 | 语法错误即失败 | 强一致 |
| Analyzer | ✓ | - | - | - | - | - | - | 同步 | 对象不存在即失败 | 强一致 |
| Planner | ✓ | - | - | - | - | - | - | 同步 | 统计信息缺失降级 | 最终一致(统计) |
| Executor | ✓ | ✓ | ✓ | ✓ | ✓ | - | ✓ | 同步 | 运行时错误可恢复 | 强一致(MVCC) |
| Heap AM | - | ✓ | - | - | ✓ | ✓ | - | 同步 | 磁盘故障致命 | 强一致(WAL) |
| Index AM | - | ✓ | - | - | ✓ | ✓ | - | 同步 | 磁盘故障致命 | 强一致(WAL) |
| Buffer Mgr | - | - | - | - | - | ✓ | - | 同步/异步 | 缓冲区满阻塞 | 强一致(LSN) |
| Transaction Mgr | - | - | - | - | - | ✓ | ✓ | 同步 | 提交失败回滚 | 强一致(CLOG) |
| WAL Writer | - | - | - | - | - | ✓ | - | 异步(后台进程) | 写入失败重试 | 强一致(fsync) |
| Checkpointer | - | ✓ | - | - | - | ✓ | - | 异步(后台进程) | 延迟完成可接受 | 最终一致 |
| Autovacuum | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 异步(后台进程) | 跳过锁定的表 | 最终一致 |
| WAL Sender | - | - | - | - | - | ✓ | - | 异步(流式传输) | 网络断开重连 | 最终一致(异步复制) |
交互说明
Analyzer → Catalog
- 目的:查询系统表验证对象存在性、获取列定义与类型
- 接口:
SearchSysCache()、systable_beginscan() - 缓存:SysCache 缓存查询结果,减少系统表访问
- 错误处理:对象不存在返回
undefined_table错误
Planner → Catalog
- 目的:读取统计信息(pg_statistic)估算代价
- 接口:
get_relation_stats() - 最终一致性:统计信息滞后(ANALYZE 更新),不影响正确性,仅影响性能
Executor → Heap AM
- 目的:顺序扫描表、插入/更新/删除元组
- 接口:
heap_beginscan()、heap_getnext()、heap_insert() - 并发:MVCC 保证快照隔离,无需行锁(除 FOR UPDATE)
Heap AM → Buffer Manager
- 目的:读取/修改数据页
- 接口:
ReadBuffer()、ReleaseBuffer()、MarkBufferDirty() - 锁:Buffer Content Lock(短暂持有,保护页面内容修改)
Heap AM → WAL
- 目的:记录数据修改日志
- 接口:
XLogInsert() - 时机:每次页面修改前必须先写 WAL(Write-Ahead)
Buffer Manager → WAL
- 目的:刷脏页前确保 WAL 已落盘
- 约束:页面的 LSN ≤ WAL 刷盘位置(保证恢复时 WAL 覆盖数据页)
Transaction Manager → CLOG
- 目的:记录事务提交/回滚状态
- 接口:
TransactionIdSetTreeStatus() - 持久化:CLOG 通过 SLRU(Simple LRU)缓存,定期刷盘
Transaction Manager → Lock Manager
- 目的:获取表锁、行锁、死锁检测
- 接口:
LockAcquire()、LockRelease() - 死锁:死锁检测器(Deadlock Detector)遍历等待图
Checkpointer → Buffer Manager
- 目的:强制刷新所有脏页到磁盘
- 时机:基于时间(
checkpoint_timeout)或 WAL 量(max_wal_size) - 策略:分批刷新,避免 I/O 峰值(
checkpoint_completion_target)
Autovacuum → Heap AM
- 目的:清理死元组、更新 FSM(Free Space Map)、更新可见性映射(VM)
- 触发:表的修改量超过阈值(
autovacuum_vacuum_threshold) - 跳过策略:如表被 VACUUM 锁定,跳过本次清理
4. 关键设计与权衡
4.1 数据一致性
MVCC(多版本并发控制)
设计选择
- PostgreSQL 采用"元组级多版本":每个元组头记录
t_xmin(创建事务)和t_xmax(删除事务) - 优势:读不阻塞写、写不阻塞读,并发度高
- 劣势:产生大量死元组,需 VACUUM 清理
事务隔离级别
| 隔离级别 | 实现机制 | 防止的异常 | 性能影响 |
|---|---|---|---|
| Read Uncommitted | 等同于 Read Committed | 脏写 | - |
| Read Committed | 每条 SQL 获取新快照 | 脏读 | 低 |
| Repeatable Read | 事务开始时获取快照 | 不可重复读、幻读(部分) | 中 |
| Serializable | SSI(Serializable Snapshot Isolation) | 所有异常 | 高(需检测冲突) |
权衡
- 默认 Read Committed:兼顾并发与一致性
- Serializable:完全避免异常,但性能开销大(需维护冲突图)
事务边界
- 显式事务:
BEGIN…COMMIT/ROLLBACK - 隐式事务:单条 SQL 自动包装为事务
- 子事务:
SAVEPOINT支持部分回滚(通过子事务 ID 链实现)
4.2 锁与并发
锁类型层次
表级锁(8 种模式)
| 锁模式 | 冲突 | 用途 |
|---|---|---|
| AccessShareLock | AccessExclusiveLock | SELECT |
| RowShareLock | ExclusiveLock, AccessExclusiveLock | SELECT FOR UPDATE |
| RowExclusiveLock | ShareLock, ShareRowExclusiveLock, Exclusive, AccessExclusive | INSERT/UPDATE/DELETE |
| ShareLock | RowExclusiveLock, ShareRowExclusiveLock, Exclusive, AccessExclusive | CREATE INDEX (CONCURRENTLY) |
| ShareRowExclusiveLock | RowExclusiveLock, Share*, Exclusive, AccessExclusive | CREATE TRIGGER |
| ExclusiveLock | 除 AccessShareLock 外所有 | REFRESH MATERIALIZED VIEW |
| AccessExclusiveLock | 所有 | DROP TABLE, TRUNCATE, VACUUM FULL |
行级锁(通过元组头实现)
- 无显式行锁表,依赖
t_xmax和t_infomask标志位 FOR UPDATE:在元组头设置HEAP_XMAX_LOCK_ONLY标志- 锁升级:高并发下可能升级为页级锁(通过
tuple lock机制)
死锁检测
算法
- 等待图法:构建事务等待关系图,检测环
- 触发时机:等待超过
deadlock_timeout(默认 1 秒) - 牺牲策略:选择"最年轻"的事务回滚(避免连锁回滚)
权衡
deadlock_timeout过小:频繁死锁检测,CPU 开销大deadlock_timeout过大:死锁发现延迟,事务长时间等待
4.3 性能关键路径与可观测性
P95 延迟优化
热路径识别
- Parser 阶段:CPU 密集(词法/语法分析)
- 优化:准备语句(Prepared Statement)缓存解析结果
- Planner 阶段:CPU 密集(路径枚举、代价计算)
- 优化:
geqo_threshold参数启用遗传算法(大 JOIN 查询)
- 优化:
- Executor 阶段:I/O 密集(表/索引扫描)
- 优化:索引覆盖扫描(Index-Only Scan)、并行查询
- Buffer Manager:锁竞争(高并发下 Buffer Mapping Lock)
- 优化:增大
shared_buffers、使用更快的存储(NVMe)
- 优化:增大
内存峰值控制
内存上下文(Memory Context)
- 层次结构:TopMemoryContext → Transaction Context → Query Context
- 优势:批量释放内存,避免内存泄漏
- 劣势:频繁小对象分配碎片化(通过 AllocSet 缓解)
内存限制参数
shared_buffers:所有进程共享(推荐系统内存的 25%)work_mem:每个查询操作(排序、哈希)私有内存maintenance_work_mem:维护操作(VACUUM、CREATE INDEX)temp_buffers:临时表缓冲区
I/O 热点
WAL 写入
- 瓶颈:同步 fsync 是串行点
- 优化:
- 组提交(Group Commit):多个事务共享一次 fsync
synchronous_commit=off:异步提交(丢失风险)- 专用 WAL 磁盘(SSD/NVMe)
表扫描
- 瓶颈:顺序扫描大表(全表扫描)
- 优化:
- 索引扫描
- 分区表(Partitioning)
- 并行顺序扫描(Parallel Seq Scan)
可观测性指标
关键指标
| 指标 | 来源 | 含义 | 阈值建议 |
|---|---|---|---|
tup_returned / tup_fetched |
pg_stat_user_tables |
扫描效率 | 比例 > 10 表示索引缺失 |
blks_hit / blks_read |
pg_stat_database |
缓冲区命中率 | > 99% |
xact_commit / xact_rollback |
pg_stat_database |
事务成功率 | 回滚率 < 1% |
deadlocks |
pg_stat_database |
死锁次数 | 应为 0 |
temp_bytes |
pg_stat_database |
临时文件使用 | < 1GB/天(大查询优化) |
checkpoints_timed vs checkpoints_req |
pg_stat_bgwriter |
检查点压力 | timed > req(避免 I/O 峰值) |
监控工具
pg_stat_statements:记录 SQL 执行统计(需加载扩展)auto_explain:自动记录慢查询执行计划pg_stat_progress_*:长时间操作进度(VACUUM、CREATE INDEX)
4.4 配置项说明
核心配置
内存相关
shared_buffers = 8GB # 共享缓冲区(推荐内存 25%)
work_mem = 64MB # 排序/哈希操作内存(每操作)
maintenance_work_mem = 1GB # 维护操作内存
effective_cache_size = 24GB # 操作系统缓存大小(优化器估算用)
并发相关
max_connections = 200 # 最大连接数(每连接约 10MB 内存)
max_worker_processes = 8 # 后台工作进程数
max_parallel_workers = 8 # 并行查询最大工作进程
max_parallel_workers_per_gather = 4 # 单个查询最大并行度
WAL 相关
wal_level = replica # WAL 级别(minimal/replica/logical)
wal_buffers = 16MB # WAL 缓冲区
checkpoint_timeout = 15min # 检查点间隔
max_wal_size = 10GB # 检查点触发的 WAL 量
min_wal_size = 2GB # 保留的最小 WAL
synchronous_commit = on # 同步提交(性能 vs 持久性权衡)
查询优化相关
random_page_cost = 1.1 # 随机读代价(SSD 降低到 1.1)
effective_io_concurrency = 200 # 并发 I/O 能力(SSD 提高)
default_statistics_target = 100 # 统计信息采样精度
5. 典型使用场景与最佳实践
5.1 场景:最小可运行入门示例
目标:从编译安装到执行第一个查询
步骤 1:编译与安装
# 下载源码
git clone https://github.com/postgres/postgres.git
cd postgres
# 配置(启用调试符号)
./configure --prefix=/usr/local/pgsql --enable-debug --enable-cassert
# 编译(使用 4 个并行任务)
make -j4
# 安装
sudo make install
步骤 2:初始化数据库集簇
# 创建数据目录
mkdir -p /usr/local/pgsql/data
# 初始化(指定编码与区域)
/usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data -E UTF8 --locale=en_US.UTF-8
步骤 3:启动数据库
# 启动 Postmaster
/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data -l logfile start
# 创建用户数据库
/usr/local/pgsql/bin/createdb mydb
步骤 4:执行查询
# 连接数据库
/usr/local/pgsql/bin/psql mydb
# 执行 SQL
mydb=# CREATE TABLE users (id int PRIMARY KEY, name text);
mydb=# INSERT INTO users VALUES (1, 'Alice');
mydb=# SELECT * FROM users;
id | name
----+-------
1 | Alice
(1 row)
核心链路追踪
psql 客户端
→ Postmaster (postmaster.c:main)
→ fork Backend 进程 (postmaster.c:BackendStartup)
→ Backend 接收 SQL (postgres.c:PostgresMain)
→ Parser (parser/parser.c:raw_parser)
→ Analyzer (parser/analyze.c:parse_analyze)
→ Planner (optimizer/planner.c:planner)
→ Executor (executor/execMain.c:ExecutorStart/ExecutorRun)
→ Heap AM (access/heap/heapam.c:heap_insert)
→ Buffer Manager (storage/buffer/bufmgr.c:ReadBuffer)
→ 返回结果 (tcop/dest.c:DestRemote)
5.2 场景:扩展点/插件接入
目标:加载自定义扩展(以 pg_stat_statements 为例)
扩展机制
PostgreSQL 支持通过共享库(.so/.dll)动态加载扩展,无需重新编译核心代码。
扩展类型
- 钩子(Hook):在核心代码预留的扩展点插入自定义逻辑
ExecutorStart_hook:查询开始时回调planner_hook:替换默认优化器
- 自定义函数:SQL 可调用的 C 函数
- 自定义数据类型:新增数据类型与操作符
- 后台工作进程:独立的后台任务(如
pg_cron)
示例:加载 pg_stat_statements
步骤 1:编译扩展
cd contrib/pg_stat_statements
make
sudo make install
步骤 2:配置加载
编辑 postgresql.conf:
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 10000
pg_stat_statements.track = all
步骤 3:重启数据库
pg_ctl restart -D /usr/local/pgsql/data
步骤 4:创建扩展对象
CREATE EXTENSION pg_stat_statements;
-- 查询 SQL 统计
SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
扩展点实现原理
以 ExecutorStart_hook 为例:
/* src/backend/executor/execMain.c */
ExecutorStart_hook_type ExecutorStart_hook = NULL;
void ExecutorStart(QueryDesc *queryDesc, int eflags) {
/* 调用钩子(如已注册) */
if (ExecutorStart_hook)
(*ExecutorStart_hook)(queryDesc, eflags);
else
standard_ExecutorStart(queryDesc, eflags); /* 默认实现 */
}
扩展注册钩子:
/* contrib/pg_stat_statements/pg_stat_statements.c */
void _PG_init(void) {
/* 注册钩子 */
prev_ExecutorStart = ExecutorStart_hook;
ExecutorStart_hook = pgss_ExecutorStart;
}
static void pgss_ExecutorStart(QueryDesc *queryDesc, int eflags) {
/* 自定义逻辑:记录查询开始时间 */
/* ... */
/* 调用下一个钩子或默认实现 */
if (prev_ExecutorStart)
prev_ExecutorStart(queryDesc, eflags);
else
standard_ExecutorStart(queryDesc, eflags);
}
5.3 场景:规模化生产部署
上线检查清单
1. 硬件配置
- CPU:推荐 8 核以上(支持并行查询)
- 内存:推荐 64GB 以上(
shared_buffers设置为 16GB) - 磁盘:
- 数据盘:SSD/NVMe,RAID 10(冗余 + 性能)
- WAL 盘:独立 SSD(避免与数据盘 I/O 竞争)
- 网络:千兆网卡(复制场景)
2. 操作系统优化
# 增大共享内存限制
sysctl -w kernel.shmmax=17179869184 # 16GB
sysctl -w kernel.shmall=4194304 # 16GB / 4KB
# 增大打开文件数
ulimit -n 65536
# 禁用透明大页(THP)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 调整脏页刷新(避免 I/O 峰值)
sysctl -w vm.dirty_background_ratio=5
sysctl -w vm.dirty_ratio=10
3. PostgreSQL 配置
# 连接池(推荐外部连接池如 pgBouncer)
max_connections = 200
# 内存(根据服务器内存调整)
shared_buffers = 16GB
effective_cache_size = 48GB
work_mem = 64MB
maintenance_work_mem = 2GB
# WAL(高可用场景)
wal_level = replica
max_wal_senders = 5
wal_keep_size = 1GB
# 检查点(平滑 I/O)
checkpoint_timeout = 15min
max_wal_size = 10GB
checkpoint_completion_target = 0.9
# 查询优化(SSD 场景)
random_page_cost = 1.1
effective_io_concurrency = 200
# 日志(便于问题诊断)
log_destination = 'csvlog'
logging_collector = on
log_directory = 'log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_min_duration_statement = 1000 # 记录超过 1 秒的查询
log_checkpoints = on
log_lock_waits = on
4. 高可用架构
主从复制(Streaming Replication)
主库 (Primary)
↓ WAL Stream
备库 1 (Standby) - 同步复制
↓ WAL Stream
备库 2 (Standby) - 异步复制
配置示例:
# 主库 postgresql.conf
wal_level = replica
max_wal_senders = 5
wal_keep_size = 1GB
synchronous_standby_names = 'standby1' # 同步复制
# 备库 postgresql.conf
hot_standby = on
max_standby_streaming_delay = 30s
故障切换
- 自动:使用 Patroni + etcd/Consul
- 手动:
pg_ctl promote提升备库为主库
5. 监控与告警
- 关键指标:
- 复制延迟:
pg_stat_replication.replay_lag - 缓冲区命中率:
pg_stat_database.blks_hit / (blks_hit + blks_read) - 慢查询:
pg_stat_statements.mean_exec_time - 锁等待:
pg_stat_activity.wait_event
- 复制延迟:
- 工具:
- Prometheus + postgres_exporter
- Grafana 可视化
- pgBadger 日志分析
6. 备份策略
- 物理备份:
pg_basebackup(全量) + WAL 归档(增量) - 逻辑备份:
pg_dump(适合小数据库) - 频率:每日全量 + 实时 WAL 归档
- 恢复测试:每月验证备份可恢复性
6. 版本演进与架构变迁
| 版本 | 发布时间 | 关键特性 | 架构影响 |
|---|---|---|---|
| 7.0 | 2000 | WAL 日志系统 | 崩溃恢复机制建立 |
| 8.0 | 2005 | PITR(时间点恢复)、Windows 支持 | WAL 归档与复制基础 |
| 9.0 | 2010 | 流复制、Hot Standby | 高可用架构成熟 |
| 9.6 | 2016 | 并行查询 | 执行器多进程架构 |
| 10 | 2017 | 声明式分区、逻辑复制 | 分区表性能优化 |
| 11 | 2018 | JIT 编译(LLVM) | 执行器表达式加速 |
| 12 | 2019 | B-Tree 索引优化、CTE 内联 | 索引空间减少、优化器增强 |
| 13 | 2020 | B-Tree 去重、并行 VACUUM | 索引效率提升 |
| 14 | 2021 | 并行查询增强、LZ4/ZSTD 压缩 | 压缩表支持 |
| 15 | 2022 | MERGE 语句、WAL 压缩 | SQL 标准完善 |
| 16 | 2023 | 逻辑复制增强、并行化改进 | 复制灵活性提升 |
7. 参考资源
官方文档
- 源码文档:
src/backend/README - 开发者 FAQ:
https://wiki.postgresql.org/wiki/Developer_FAQ
推荐阅读
- 《PostgreSQL 修炼之道》:张树杰
- 《PostgreSQL 内核分析》:彭智勇
- 论文:《The Design of Postgres》(Michael Stonebraker, 1986)
调试工具
- GDB:
gdb --args postgres --single -D /path/to/data postgres - SystemTap:跟踪内核函数调用
pg_waldump:解析 WAL 日志pageinspect扩展:查看页面内部结构