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),节点类型如 SeqScanIndexScanNestLoop

执行阶段(步骤 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() 函数:
    1. 选择目标页面:优先使用 FSM(Free Space Map)查找有空闲空间的页
    2. 在页面中写入元组:
      • 元组头(HeapTupleHeader)包含 t_xmin = 12345(插入事务 ID)
      • t_xmax = 0(未被删除)
      • t_ctid(指向自身,元组标识符)
    3. 生成 WAL 记录:
      • 记录类型:XLOG_HEAP_INSERT
      • 包含页面修改前镜像(FPI,首次修改时)或仅记录增量
    4. 标记页面为脏页,记录页面的 LSN(Log Sequence Number)

WAL 写入机制

  • WAL 缓冲区(WAL Buffers)在共享内存中,大小由 wal_buffers 配置
  • 写入时机:
    • 事务提交时强制刷盘
    • WAL 缓冲区满时
    • WAL Writer 进程周期性刷新(wal_writer_delay
  • 保证:WAL 落盘后数据修改才可见(Write-Ahead)

事务提交(步骤 17-23)

  • COMMIT 命令触发提交流程:
    1. 在 CLOG 中标记 XID=12345 为 COMMITTED
    2. 生成 XLOG_XACT_COMMIT WAL 记录
    3. 同步刷新 WAL 到磁盘(fsyncfdatasync
    4. 释放事务持有的锁
    5. ProcArray 移除事务信息

关键约束点

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” 的组合:

  1. 旧元组不立即删除,而是标记 t_xmax 为当前事务 ID
  2. 新元组插入到同页或新页,t_xmin 为当前事务 ID
  3. 通过元组链(t_ctid)关联旧版本和新版本

可见性规则

事务快照包含:

  • xmin:快照创建时最小的活跃事务 ID
  • xmax:下一个未分配的事务 ID
  • xip[]:快照创建时所有活跃事务 ID 列表

元组对事务 T 可见的条件:

  • t_xmin < T.xmint_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 UPDATE
    • FOR 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:完全避免异常,但性能开销大(需维护冲突图)

事务边界

  • 显式事务:BEGINCOMMIT/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_xmaxt_infomask 标志位
  • FOR UPDATE:在元组头设置 HEAP_XMAX_LOCK_ONLY 标志
  • 锁升级:高并发下可能升级为页级锁(通过 tuple lock 机制)

死锁检测

算法

  • 等待图法:构建事务等待关系图,检测环
  • 触发时机:等待超过 deadlock_timeout(默认 1 秒)
  • 牺牲策略:选择"最年轻"的事务回滚(避免连锁回滚)

权衡

  • deadlock_timeout 过小:频繁死锁检测,CPU 开销大
  • deadlock_timeout 过大:死锁发现延迟,事务长时间等待

4.3 性能关键路径与可观测性

P95 延迟优化

热路径识别

  1. Parser 阶段:CPU 密集(词法/语法分析)
    • 优化:准备语句(Prepared Statement)缓存解析结果
  2. Planner 阶段:CPU 密集(路径枚举、代价计算)
    • 优化:geqo_threshold 参数启用遗传算法(大 JOIN 查询)
  3. Executor 阶段:I/O 密集(表/索引扫描)
    • 优化:索引覆盖扫描(Index-Only Scan)、并行查询
  4. 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)动态加载扩展,无需重新编译核心代码。

扩展类型

  1. 钩子(Hook):在核心代码预留的扩展点插入自定义逻辑
    • ExecutorStart_hook:查询开始时回调
    • planner_hook:替换默认优化器
  2. 自定义函数:SQL 可调用的 C 函数
  3. 自定义数据类型:新增数据类型与操作符
  4. 后台工作进程:独立的后台任务(如 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 扩展:查看页面内部结构