正德厚生,臻于至善

PostgreSQL 实例架构与进程机制

基于 PostgreSQL 实例架构图整理,覆盖:进程模型、共享内存、后台辅助进程、WAL 与 Checkpoint 协作、MVCC 与 Autovacuum 机制,并附 Oracle 对照表。


一、整体架构:一连接一进程的多进程模型

PostgreSQL 采用多进程模型(区别于 MySQL 的多线程模型),一个实例由以下部分组成:

  • 1 个主进程(postmaster)
  • N 个后端工作进程(每个客户端连接独占一个)
  • 7 类后台辅助进程
  • 一块巨大的 shared memory
  • 磁盘上的 database cluster

核心特征:

  • 客户端连接请求 → postmaster 认证 → fork 一个 backend 进程专门服务
  • 所有进程通过 shared memory 交换数据、协调锁、传递 WAL
  • 一个 backend 崩溃会导致 postmaster 重启所有 backend(fail-fast 设计)

对 Oracle 背景的人来说,这几乎就是 Dedicated Server 模式的 Oracle 实例。


二、主进程与后端进程

2.1 postgres (server process) —— postmaster 主进程

整个实例的”总管”,负责:

  1. 监听端口(默认 5432),接收 connection request
  2. 认证(md5 / scram-sha-256 / trust / peer 等)
  3. fork backend 进程,把认证通过的连接交给它
  4. 监管所有子进程的启动、监控、重启
  5. 处理 smart / fast / immediate 三种关停模式
  6. 任何子进程异常崩溃时,毁掉 shared memory 并重启所有 backend

2.2 postgres (backend process) —— 后端工作进程

每个客户端连接对应一个 backend,真正干活:

  1. SQL 全链路执行:parse → rewrite → planner → executor
  2. 直接读写 shared memory 中的 buffer
  3. 产生 WAL 记录写入 WAL buffer
  4. 客户端断开即退出,资源由 postmaster 回收

关键特性:

  • 进程间无共享状态,全靠 shared memory 通信
  • 单个 backend 段错误会拖垮全实例(因为共享内存可能已损坏)

三、Shared Memory 共享内存组成

区域作用类比 Oracle
Shared Buffers缓存数据页(表/索引页),默认 128MB,生产建议物理内存 25%Buffer Cache
WAL BuffersWAL 日志缓冲Redo Log Buffer
CLOG (Commit Log)事务提交状态(提交/中止/子事务)Undo 段中的事务状态位
Lock Manager轻量锁(LWLock)、重型锁(锁表)Enqueue / Latch
Buffer Mapping哈希表,记录哪个磁盘页在哪个 buffer 槽X$BH
WAL Insert/MagicWAL 插入点、写指针、flush 指针Redo 线程

四、七大后台辅助进程

4.1 Background Writer(后台写进程)

  • 职责:周期性把 shared buffers 中的脏页批量、预防性刷到磁盘数据文件
  • 目的:避免某个 backend 在需要空闲 buffer 时被迫自己刷脏页(拖慢单次 SQL)
  • 触发:bgwriter_delay=200ms,按脏页/分配槽位阈值写
  • 类比 Oracle:DBWn,定位是”日常保洁”

4.2 Checkpointer(检查点进程)

  • 职责:触发一次 checkpoint,把当时所有脏页刷盘,推进 redo 点
  • 做什么:
    1. 把 shared buffers 中所有脏页写数据文件
    2. 把 WAL flush 到最新
    3. 更新控制文件(checkpoint REDO pointer)
    4. 记录 checkpoint 位置到 WAL
  • 触发:
    • 时间触发:checkpoint_timeout=5min
    • 日志量触发:max_wal_size=1GB
    • 手动 CHECKPOINT;
  • 意义:崩溃恢复从最近 checkpoint 开始重放 WAL,checkpoint 越频繁恢复越快,但 IO 压力越大
  • 类比 Oracle:CKPT + 一次 DBWn 全量刷脏

4.3 WAL Writer(WAL 日志写进程)

  • 职责:把 WAL buffer 中的 WAL 记录刷到 pg_wal/ 下的 WAL 段文件
  • 触发:
    • wal_writer_delay=200ms 周期性写
    • 事务 commit 且 synchronous_commit=on 时强制立刻刷盘(group commit)
  • 类比 Oracle:LGWR

4.4 Autovacuum(自动清理进程)

  • 职责:
    • 清理 dead tuple(MVCC 下 UPDATE/DELETE 残留的旧版本行)
    • 回收空闲空间、防止表膨胀(bloat)
    • 自动跑 ANALYZE 更新统计信息
    • 防止事务 ID 回卷(anti-wraparound)
  • 组成:autovacuum launcher(全局调度)+ 多个 autovacuum worker(按表干活)
  • 必要性:PG 的 MVCC 不立刻删旧版本,不 vacuum 会导致表膨胀、事务 ID 耗尽数据库强制 shutdown
  • 类比 Oracle:无直接对应物,是 PMON 清理 + 统计信息收集的混合体

4.5 Statistics Collector(统计信息收集进程)

  • 职责:收集表/索引行数、IO 次数、活元组数、vacuum 次数等统计
  • 用途:
    • 给查询规划器做成本估算(执行计划准不准的根基)
    • 提供 pg_stat_activity、pg_stat_database、pg_stat_user_tables 等视图
  • 特点:通过消息传递收集,非实时;PG15 起部分统计迁移到 shared memory

4.6 Log Writer(日志写进程)

  • 职责:把日志缓冲区写到 pg_log/ 日志文件
  • PG10 引入,替代原来每个 backend 自己写日志的方式,避免大量 backend 并发打日志造成 IO 竞争

4.7 Archiver(归档进程)

  • 前提:archive_mode=on
  • 职责:把已写完、不再需要的 WAL 段文件按 archive_command 拷贝到归档目录
  • 用途:PITR 时间点恢复、流复制/物理备份的基础
  • 类比 Oracle:ARCn

五、WAL 与 Checkpoint 协作机制(深入)

5.1 WAL 是什么

  • 存储位置:$PGDATA/pg_wal/,默认每个段文件 16MB(PG11 起可在 initdb 时用 --wal-segsize 调整)
  • 每条 WAL 记录有全局唯一的 LSN(Log Sequence Number),8 字节,是整个实例的字节偏移量,单调递增
  • WAL 记的不是 SQL,而是物理级别的”对哪个 page 的哪个 offset 做了什么修改”,类似 Oracle 的 change vector
  • 例:一条 INSERT 记录的是”在 rel 1663/12345 的 block 0x2a 偏移 0x40 处插入 tuple (xmin=100, …)”
  • 为什么记物理不记逻辑:崩溃恢复时数据页可能本身就是半截写坏的,物理重放能幂等地把页拉回一致状态

5.2 LSN 坐标体系

WAL 写入流:
┌──────────────────────────────────────────────────┐
│  已可回收  |  redo点  |  已产生WAL  | 已flush   │
│  (归档/复用) ↑         ↑            ↑            │
│           redo_ptr   write_ptr    flush_ptr     │
└──────────────────────────────────────────────────┘
                          ↑
                 崩溃恢复从 redo_ptr 开始重放
  • redo_ptr:最近一次 checkpoint 的起点
  • write_ptr:WAL 已写入 buffer 的位置
  • flush_ptr:WAL 已真正 fsync 到磁盘的位置
  • 流复制、复制 slot、pg_stat_replication 看到的全是这几个指针的差值

5.3 事务提交时的刷盘逻辑(Group Commit)

  1. backend 产生 WAL 记录 → 写入 WAL buffer(内存)
  2. synchronous_commit=on(默认)时,commit 必须等 WAL 真的 fsync 到磁盘
  3. 多个事务在 wal_writer_delay=200ms 窗口内一起提交 → WAL writer 一次 fsync 刷多条,即 group commit

实战推论:”提交慢”绝大多数情况是 WAL 刷盘慢,不是数据页写慢。把 pg_wal 单独放到一块 SSD,是 PG 性价比最高的优化之一。

5.4 Checkpoint 分阶段动作

一次 checkpoint 不是瞬间动作,而是:

  1. 写一条 checkpoint record 到 WAL(记录当前 redo 起点 = redo_ptr)
  2. 扫描 shared buffers 中所有脏页
  3. 把脏页刷到数据文件——PG 8.4 之后不是 checkpointer 一个人刷,而是让 backend 和 bgwriter 一起参与分摊 IO
  4. 更新控制文件,记录最新 checkpoint 位置
  5. 回收旧 WAL:比 redo_ptr 更早的 WAL 段就可以归档/复用

5.5 Checkpoint 卡顿与调优

  • 脏页太多时集中刷盘 → IO 尖峰 → 业务抖动
  • 关键参数:
    • checkpoint_completion_target=0.9(默认 0.5):把刷脏动作均匀摊到整个 checkpoint 间隔,而不是最后一刻猛刷
    • max_wal_size:太小 → checkpoint 太频繁;太大 → 崩溃恢复慢、磁盘占用大
    • checkpoint_timeout=5min:时间触发
  • 经验值:OLTP 场景 max_wal_size 调到 4GB–16GB,completion_target=0.9

六、MVCC 与 Autovacuum 机制(深入)

6.1 每行数据自带两个隐藏字段

每个 tuple 都有:

  • xmin:插入这行的事务 ID
  • xmax:删除/更新这行的事务 ID(未删除则为 0)

一个事务读一行时,根据自己的 snapshot 判断这行对它可不可见。

6.2 UPDATE 不是原地改,是”插新版本 + 标旧版本”

这是 PG 和 Oracle 最根本的架构分歧:

Oracle:UPDATE 一行
  └─ 在原 block 上原地改
     └─ 旧版本扔到 undo 段
     └─ 读一致性:current block + undo 拼接

PostgreSQL:UPDATE 一行
  └─ INSERT 一个新 tuple(xmin = 当前事务)
  └─ 旧 tuple 的 xmax 被设为当前事务
  └─ 旧 tuple 留在原 data block 里,变成 dead tuple

6.3 Dead Tuple 与 Vacuum

  • 旧版本 tuple 不会立刻删,因为可能还有长事务需要它
  • 当所有能看到它的事务都结束后,它就变成 dead tuple——占着空间但没人用
  • 普通 VACUUM 做什么:
    • 扫描每个 page,把 dead tuple 标记为可重用(记到 FSM 空闲空间映射)
    • 更新 visibility map
    • 不把空间还给 OS
  • VACUUM FULL:真正重写表、归还 OS,但会锁表,生产慎用
  • 不 vacuum 的后果:表膨胀(bloat)、索引膨胀、全表扫描越来越慢、buffer 缓存命中率下降

6.4 XID 防回卷(Wraparound)—— PG 的”生死线”

  • 事务 ID 是 32 位无符号整数,最大约 20 亿
  • 当一个老事务 XID 比新事务老 20 亿以上时,会发生 XID wraparound(回卷)——老事务在数值上”比新事务还新”,可见性判断直接错乱
  • PG 的对策叫 freeze:把足够老的 tuple 的 xmin 改成特殊值 FrozenXID,让它对所有事务都可见,不再参与 XID 比较
  • autovacuum 会在以下情况强制启动(即使 autovacuum=off):
    • 表的”年龄”超过 autovacuum_freeze_max_age(默认 2 亿)
    • 整个库最老 XID 距离回卷只剩 100 万事务时
  • 如果不及时 freeze,PG 会主动 safe shutdown,拒绝任何连接,逼你手动 vacuum

这是 PG DBA 最不能掉以轻心的事——比慢查询严重得多。

6.5 Autovacuum 内部调度

  • autovacuum launcher:全局调度,每 autovacuum_naptime(默认 1min)扫一遍哪些表需要 vacuum
  • autovacuum worker:真正干活的进程,最多 autovacuum_max_workers(默认 3)个
  • 触发某张表 vacuum 的条件: dead_tuple 数量 > autovacuum_vacuum_threshold(默认50) + autovacuum_vacuum_scale_factor(默认0.2) × 表行数
  • 100 万行的表要攒到 20 万 dead tuple 才触发一次 autovacuum——大表往往等太久,生产环境常把这两个参数调小

七、关键流程串联

7.1 一次 UPDATE 从 SQL 到落盘

client
  │ SQL
  ▼
backend process ──修改 shared buffer 中的数据页(产生脏页)
       │
       ├──产生 WAL 记录 → WAL buffer
       │                    │
       │  commit 时          ▼
       │            WAL writer 把 WAL 刷盘(fsync)
       │                    │
       │            返回"提交成功"给客户端(synchronous_commit=on)
       ▼
脏页留在 shared buffer,稍后由:
   ├── background writer 周期性刷脏(日常)
   └── checkpointer 周期性全量刷脏(大扫除)

核心原则:WAL 先于数据页落盘(Write-Ahead Logging)。即使数据页还没刷盘就崩溃,重启时也能从 checkpoint 位置重放 WAL 把数据找回来。

7.2 数据库崩溃重启流程

  1. postmaster 启动,读控制文件发现上次非正常关闭
  2. 从最近一次 checkpoint 的 REDO 点开始重放 WAL
  3. 把所有已提交但未刷盘的数据页重放出来
  4. 未提交事务回滚
  5. 接受新连接

max_wal_size 决定了两次 checkpoint 之间的距离,也就决定了崩溃恢复时长。


八、PostgreSQL 与 Oracle 对照表

PostgreSQLOracle说明
postmaster (server process)PMON + listener主进程
backend processdedicated server process一连接一进程
shared memorySGA实例内存
shared buffersbuffer cache数据页缓存
WAL buffers / WAL writerredo log buffer / LGWR预写日志
background writerDBWn脏页刷盘
checkpointerCKPT检查点
autovacuum(无直接对应)MVCC 死元组清理
statistics collectorAWR/ASH 收集统计信息
archiverARCn日志归档
WALredo log重做日志
dead tuple + vacuumundo + block cleanout旧版本清理
多进程模型多进程 + 线程池PG 每个连接一个 OS 进程

MVCC 架构哲学差异

维度OraclePostgreSQL
UPDATE 行为原地修改 block插入新版本,旧版本变 dead tuple
旧版本存放undo 表空间就在主数据 block 里
读一致性current block + undo 拼接tuple 自带 xmin/xmax,按 snapshot 判断
旧版本回收PMON/SMON 自动回收 undo必须靠 autovacuum 清理 dead tuple
事务 IDSCN,48 位,不用担心回卷XID,32 位,必须 freeze 防回卷
统计信息自动收集任务 / DBMS_STATSautovacuum 顺带做 ANALYZE
表膨胀风险低(undo 分离)高(bloat 是日常头号问题)

九、实战要点与常见问题

  1. 为什么用进程不用线程:稳定(一个进程崩了不至于整个实例崩)+ 锁模型简单;代价是上下文切换贵,连接数上千就吃不住——这就是 PgBouncer 连接池在 PG 生态几乎必装的原因。
  2. bgwriter 和 checkpointer 为什么要分两个进程:bgwriter 是”少量多次”维持 buffer 池干净,checkpointer 是”周期性大动作”推进恢复点,分工不同,不能合并。
  3. autovacuum 是 PG DBA 头号日常:表膨胀、XID 回卷、统计信息滞后,几乎都和它有关;autovacuum_vacuum_scale_factor、autovacuum_naptime、maintenance_work_mem 是最常调的三个参数。
  4. 单 backend 崩溃会拖垮全实例:和 MySQL 一个连接崩了不影响其他连接不同,PG 是 fail-fast,宁可全部重启也不带着可能损坏的 shared memory 跑。
  5. WAL 放 SSD 是性价比最高的优化:提交延迟的瓶颈通常在 WAL fsync,不在数据页写盘。
  6. max_wal_size 与崩溃恢复时间的权衡:太小 → 频繁 checkpoint、IO 抖动;太大 → 崩溃恢复慢、磁盘占用大。OLTP 经验值 4GB–16GB。

十、一句话总结

  • WAL + Checkpoint:解决”崩溃了怎么恢复”,靠的是先日志后数据 + 周期性推进 redo 点;WAL 放 SSD、调大 max_wal_size + completion_target=0.9,是稳定性和性能的关键。
  • MVCC + Autovacuum:解决”并发读写不锁”的代价,靠的是版本链 + 后台清理;代价是必须盯着表膨胀和 XID 回卷——这是 PG DBA 区别于其他 DBA 的核心日常。
赞(0) 打赏
未经允许不得转载:徐万新之路 » PostgreSQL 实例架构与进程机制

支持快讯、专题、百度收录推送、人机验证、多级分类筛选器,适用于垂直站点、科技博客、个人站,扁平化设计、简洁白色、超多功能配置、会员中心、直达链接、文章图片弹窗、自动缩略图等...

联系我们

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续提供更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫

登录

找回密码

注册