强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

03 - 架构原理

第 03 章 · 架构原理

理解 PostgreSQL 的内部架构,是做好性能调优和故障排查的基础。本章将深入讲解其进程模型、内存结构、存储机制和核心子系统。


3.1 整体架构总览

PostgreSQL 采用 客户端/服务器 架构,核心由以下组件构成:

┌──────────────────────────────────────────────────────┐
│                    客户端应用                          │
│              (psql / pgAdmin / 驱动程序)               │
└──────────────┬───────────────────────────┬────────────┘
               │ TCP/Unix Socket           │
               ▼                           ▼
┌──────────────────────┐    ┌──────────────────────────┐
│    Postmaster         │    │   Backend Process         │
│   (主守护进程)         │    │   (每个连接一个进程)        │
│   PID 1              │───→│   执行查询、返回结果         │
└──────────┬───────────┘    └────────────┬──────────────┘
           │                             │
           ▼                             ▼
┌──────────────────────────────────────────────────────┐
│                  Shared Memory                         │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│  │ Shared   │ │   WAL    │ │  CLOG   │ │ Other   │ │
│  │ Buffers  │ │ Buffers  │ │         │ │         │ │
│  └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
└──────────────────────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────────────┐
│                  Background Workers                    │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│  │ BG Writer│ │Checkpointer│ │ Autovacuum│ │WAL Writer│
│  └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐              │
│  │ Stats    │ │WAL Sender│ │Logical   │              │
│  │ Collector│ │          │ │Rep Worker│              │
│  └──────────┘ └──────────┘ └──────────┘              │
└──────────────────────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────────────┐
│                    磁盘存储                            │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│  │ Data     │ │  WAL     │ │  CLOG   │ │ Other   │ │
│  │ Files    │ │  Files   │ │         │ │         │ │
│  └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
└──────────────────────────────────────────────────────┘

3.2 进程模型

PostgreSQL 使用 多进程模型(而非多线程),每个客户端连接会 fork 一个独立的 Backend 进程。

核心进程

进程作用数量
Postmaster主守护进程,监听端口,fork 子进程1
Backend处理客户端查询的进程每连接 1 个
Background Writer (BG Writer)将脏页从 Shared Buffers 写入磁盘1
Checkpointer执行检查点,确保数据持久化1
WAL Writer将 WAL 缓冲区写入 WAL 文件1
Autovacuum Launcher启动自动清理工作者1
Autovacuum Worker执行 VACUUM 和 ANALYZE可配置(默认 3)
WAL Archiver归档 WAL 文件(用于 PITR)0 或 1
WAL Sender流复制:向从库发送 WAL每个从库 1 个
WAL Receiver流复制:从主库接收 WAL(从库)0 或 1
Stats Collector收集统计信息1(PG 16+ 融入主进程)
Logical Replication Worker逻辑复制工作进程按需

查看进程

-- 查看所有后端进程
SELECT pid, usename, datname, client_addr, state, query, backend_start
FROM pg_stat_activity;

-- 查看各类进程数
SELECT backend_type, count(*)
FROM pg_stat_activity
GROUP BY backend_type;

-- 终止某个连接
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE datname = 'mydb' AND pid <> pg_backend_pid();

连接开销

每个 Backend 进程约占用 5-10MB 内存,大量连接时资源开销显著。这正是为什么生产环境需要连接池(如 PgBouncer)。

1000 连接 × 10MB = ~10GB 仅连接开销
使用 PgBouncer 后:50 活跃连接 × 10MB = ~500MB

⚠️ 注意事项:PostgreSQL 的多进程模型意味着 max_connections 不宜设置过大(通常不超过 500),应通过连接池复用连接。


3.3 内存结构

Shared Memory

共享内存是所有进程共享的内存区域,在 PostgreSQL 启动时分配。

组件参数作用推荐大小
Shared Buffersshared_buffers数据页缓存总内存的 25%
WAL Bufferswal_buffersWAL 写入缓冲64MB(自动计算)
CLOG Buffers事务提交状态缓存自动管理
Lock Space锁信息存储按需
Proc Array进程状态数组按需

Per-Session Memory

每个 Backend 进程独立使用的内存:

组件参数作用推荐大小
Work Memwork_mem排序、哈希、Merge Join4-64MB
Maintenance Work Memmaintenance_work_memVACUUM、CREATE INDEX256MB-1GB
Temp Bufferstemp_buffers临时表缓存8MB

📌 关键概念work_mem每个操作的内存限制,不是每个连接。一条复杂查询可能同时使用多个 work_mem(例如多个排序操作)。总内存消耗可能 = work_mem × 操作数 × 并发连接数


3.4 存储系统

数据目录结构

$ ls -la /var/lib/postgresql/17/main/
PG_VERSION          # 版本标识
postgresql.conf     # 主配置
pg_hba.conf         # 认证配置
postgresql.auto.conf # ALTER SYSTEM 配置
base/               # 数据库文件(每个子目录对应一个数据库)
global/             # 共享系统表(如 pg_database)
pg_wal/             # WAL 文件
pg_xact/            # 事务提交状态(CLOG)
pg_tblspc/          # 表空间符号链接
pg_stat_tmp/        # 临时统计文件
pg_log/             # 日志(可能在其他位置)

数据文件组织

base/
├── 1/               # template1 数据库
├── 13751/           # template0 数据库
├── 16384/           # postgres 数据库
└── 16385/           # mydb 数据库
    ├── 1234         # 表的数据文件(relfilenode)
    ├── 1234.1       # 表的第一个扩展段
    ├── 1234_fsm     # Free Space Map
    ├── 1234_vm      # Visibility Map
    └── 1234_init    # 未初始化的表

每个表对应一个或多个文件(1GB 为单位的扩展段),文件名为该表的 relfilenode

-- 查看表的 relfilenode
SELECT relfilenode FROM pg_class WHERE relname = 'mytable';

-- 查看表的物理大小
SELECT pg_size_pretty(pg_total_relation_size('mytable'));

-- 查看表的文件路径
SELECT pg_relation_filepath('mytable');

页(Page / Block)

PostgreSQL 的基本 I/O 单位是 ,默认大小为 8KB(编译时可选 1-32KB)。

┌─────────────────────────────────────────────┐
│                Page (8KB)                    │
├─────────────────────────────────────────────┤
│ PageHeaderData     (24 bytes)               │
│  - pd_lsn          (最后修改的 WAL 位置)     │
│  - pd_checksum     (校验和)                  │
│  - pd_lower        (空闲空间起始)            │
│  - pd_upper        (空闲空间结束)            │
│  - pd_special      (特殊空间起始)            │
│  - ...                                      │
├─────────────────────────────────────────────┤
│ ItemIdData (行指针数组)                       │
│  [1] offset, length, flags                  │
│  [2] offset, length, flags                  │
│  ...                                        │
├─────────────────────────────────────────────┤
│ Free Space                                 │
├─────────────────────────────────────────────┤
│ Tuple Data (行数据,从下往上增长)              │
│  ...                                        │
│  [Tuple 2]                                  │
│  [Tuple 1]                                  │
├─────────────────────────────────────────────┤
│ Special Space (索引页专用)                    │
└─────────────────────────────────────────────┘

3.5 MVCC(多版本并发控制)

MVCC 是 PostgreSQL 实现高并发的核心机制。每个事务看到的是数据的一个快照(Snapshot),而非当前实时数据。

实现原理

每行数据有隐藏的系统字段:

字段含义
xmin插入该行的事务 ID
xmax删除/更新该行的事务 ID(0 表示未删除)
ctid行的物理位置(页号, 行号)
-- 查看隐藏系统字段
SELECT xmin, xmax, ctid, * FROM mytable;

--  xmin  | xmax | ctid  | id | name
-- -------+------+-------+----+------
--  1000  |    0 | (0,1) |  1 | Alice    ← 活跃行
--  1000  | 1001 | (0,2) |  2 | Bob      ← 已被事务 1001 删除

UPDATE 的实现

PostgreSQL 中的 UPDATE 实际上是 DELETE + INSERT

UPDATE users SET name = 'Alice_v2' WHERE id = 1;

-- 实际操作:
-- 1. 旧行 (id=1, name='Alice') 的 xmax 设为当前事务 ID(标记为删除)
-- 2. 在同一页或其他页插入新行 (id=1, name='Alice_v2')
-- 3. 更新索引指向新行

这意味着 UPDATE 会产生死元组(Dead Tuple),需要 VACUUM 清理。

⚠️ 注意事项:频繁的 UPDATE 会导致表膨胀(Table Bloat),这是 PostgreSQL 性能下降的常见原因之一。需要定期 VACUUM 或使用 pg_repack。


3.6 WAL(Write-Ahead Logging)

WAL 是 PostgreSQL 保证数据持久性和一致性的核心机制。

基本原理

事务提交时:
1. WAL 记录先写入 WAL Buffer
2. WAL Buffer 刷入 WAL 文件(fsync)
3. 返回客户端 "COMMIT 成功"
4. 后续才将数据页写入数据文件

┌──────────┐     ┌──────────┐     ┌──────────┐
│ Backend  │────→│ WAL      │────→│ WAL      │
│ Process  │     │ Buffer   │     │ File     │  ← 先写这里
└──────────┘     └──────────┘     └──────────┘
     │                                │
     ▼                                ▼ (延迟写入)
┌──────────┐                     ┌──────────┐
│ Shared   │                     │ Data     │
│ Buffers  │────────────────────→│ File     │  ← 后写这里
└──────────┘                     └──────────┘

WAL 配置

-- WAL 级别
SHOW wal_level;
-- replica  - 支持流复制和 PITR(默认)
-- logical  - 支持逻辑复制(需要时开启)

-- WAL 相关参数
SHOW max_wal_size;     -- 自动 checkpoint 的 WAL 上限(默认 1GB)
SHOW min_wal_size;     -- WAL 文件保留的最小空间(默认 80MB)
SHOW wal_buffers;      -- WAL 缓冲区大小(默认 -1 自动计算)
SHOW checkpoint_timeout; -- 自动 checkpoint 间隔(默认 5min)
SHOW checkpoint_completion_target; -- checkpoint 平滑写入比例(默认 0.9)

查看 WAL 信息

-- 当前 WAL 位置
SELECT pg_current_wal_lsn();

-- WAL 使用情况
SELECT
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')) AS total_wal,
    count(*) AS wal_file_count
FROM pg_ls_waldir();

-- WAL 文件列表
SELECT name, size, modification
FROM pg_ls_waldir()
ORDER BY modification DESC
LIMIT 10;

3.7 Checkpoint(检查点)

检查点是将 Shared Buffers 中的脏页强制写入数据文件的操作,确保数据持久化。

检查点触发条件

条件参数默认值
WAL 积累超过阈值max_wal_size1GB
距上次检查点超时checkpoint_timeout5min
手动触发CHECKPOINT 命令
关闭数据库pg_ctl stop -m fast

检查点对性能的影响

检查点期间会产生大量磁盘 I/O,可能导致性能抖动。

-- 监控检查点活动
SELECT
    checkpoints_timed,          -- 超时触发的检查点次数
    checkpoints_req,            -- 手动/WAL 积累触发的次数
    buffers_checkpoint,         -- 检查点写入的缓冲区数
    buffers_backend,            -- 后端进程直接写入的缓冲区数
    checkpoint_write_time,      -- 检查点写入耗时 (ms)
    checkpoint_sync_time        -- 检查点 fsync 耗时 (ms)
FROM pg_stat_bgwriter;

💡 技巧:如果 checkpoints_req 远大于 checkpoints_timed,说明 max_wal_size 设置过小,应该增大。理想情况是检查点由超时触发(checkpoints_timed),而非由 WAL 积累触发。


3.8 共享缓冲区(Shared Buffers)读写流程

读取数据:
1. Backend 进程请求某个数据页
2. 先检查 Shared Buffers 中是否有该页(缓存命中)
   ├── 命中 → 直接读取(内存操作,极快)
   └── 未命中 → 从磁盘读入 Shared Buffers → 返回
3. 后续读取同一页面即可缓存命中

写入数据:
1. Backend 修改 Shared Buffers 中的数据页(标记为脏页)
2. 不会立即写入磁盘
3. 由以下进程负责写入:
   - Checkpointer:检查点时批量写入
   - BG Writer:后台持续写入(LRU 策略)
   - Backend:无法分配缓冲区时被迫写入(最后手段)
-- 查看缓存命中率(应 > 99%)
SELECT
    sum(blks_hit) AS hits,
    sum(blks_read) AS reads,
    round(sum(blks_hit)::numeric / NULLIF(sum(blks_hit) + sum(blks_read), 0) * 100, 2) AS hit_ratio
FROM pg_stat_database
WHERE datname = current_database();

3.9 进程间通信

机制用途
共享内存数据页、WAL、锁信息等
信号量进程同步、轻量锁
LWLock轻量级锁(保护内部数据结构)
Spinlock自旋锁(极短等待)
LISTEN/NOTIFY应用层通知机制
-- LISTEN/NOTIFY 示例
-- 会话 1:监听
LISTEN my_channel;

-- 会话 2:发送通知
NOTIFY my_channel, '{"event": "user_created", "id": 42}';

-- 会话 1:接收通知
-- 收到异步通知

3.10 架构小结

┌─ 客户端层 ──────────────────────────────────┐
│  psql / 驱动程序 / ORM                       │
└─────────────────────────────────────────────┘
         │ 连接(TCP / Unix Socket)
         ▼
┌─ 连接层 ────────────────────────────────────┐
│  Postmaster → fork → Backend Process         │
│  (建议使用连接池 PgBouncer 减少 fork)         │
└─────────────────────────────────────────────┘
         │
         ▼
┌─ 查询处理层 ────────────────────────────────┐
│  Parser → Analyzer → Rewriter                │
│  → Planner/Optimizer → Executor              │
└─────────────────────────────────────────────┘
         │
         ▼
┌─ 存储层 ────────────────────────────────────┐
│  Shared Buffers ←→ Disk (Data Files)         │
│  WAL Buffer    ←→ Disk (WAL Files)           │
│  CLOG          ←→ Disk (CLOG Files)          │
└─────────────────────────────────────────────┘

业务场景

场景需关注的架构组件
高并发 OLTP连接池 + MVCC + Shared Buffers 调优
大量写入WAL 配置 + Checkpoint 调优 + BG Writer
数据恢复WAL 归档 + PITR 流程
性能瓶颈排查Shared Buffers 命中率 + 进程状态 + 锁等待
主从复制WAL Sender/Receiver + 流复制延迟

扩展阅读