正德厚生,臻于至善

PostgreSQL 物理架构

对应课程图:database cluster → $PGDATA/ 基目录 → base/ global/ pg_logical/ pg_stat/ pg_tblspc/ → 数据库子目录 → 表/索引/FSM/VM 数据文件。
本文按”实例聚簇(cluster)→ 数据目录 → 数据库 → 表/索引 → 物理文件“自顶向下展开。


0. 一张图看懂整体分层

PostgreSQL Cluster(数据库聚簇,一个 instance 管理一个 PGDATA)
│
└── $PGDATA/                        ← 基目录(数据区根,initdb 初始化)
    ├── postgresql.conf / pg_hba.conf / pg_ident.conf   ← 配置文件
    ├── global/                     ← 聚簇级共享系统目录(pg_database 等)
    ├── base/                       ← 每个数据库一个子目录(OID 命名)
    │   ├── 1/          (template0)
    │   ├── 12891/      (template1)
    │   ├── 12896/      (postgres)
    │   ├── 16384/      (sampledb)
    │   └── 16385/      (testdb)
    │       └── 18740            ← 表 relfilenode(sampletbl 的数据文件)
    │           ├── 18740        主数据文件(堆表)
    │           ├── 18740_fsm    Free Space Map(空闲空间映射)
    │           ├── 18740_vm     Visibility Map(可见性映射)
    │           └── 18740_init   (仅 unlogged 表的 init fork)
    ├── pg_wal/                     ← 预写日志(旧版 pg_xlog)
    ├── pg_logical/                 ← 逻辑解码/复制槽状态
    ├── pg_stat/  pg_stat_tmp/      ← 统计信息持久化
    ├── pg_tblspc/                  ← 表空间符号链接(指向自定义存储路径)
    ├── pg_commit_ts/ pg_notify/ pg_serial/ pg_snapshots/ pg_subtrans/ pg_multixact/
    └── postmaster.pid / postmaster.opts

核心概念:

  • Cluster(聚簇)≠ MySQL 实例里多个 database 的松散集合:PG 的一个实例(postmaster/backend 进程树)只对应一个 $PGDATA,里面”装”着多个 database。
  • Database 之间物理隔离:每个 database 数据落在 base/<dbOID>/(或表空间下),不能跨库直接 SELECT * FROM db2.tbl,这是与 Oracle”一个实例多表空间”、MySQL”一个实例多库可互相访问”的关键区别。
  • OID / relfilenode:系统用 OID 标识对象,真正落盘的文件名是 relfilenode(不一定等于 OID,VACUUM FULL/REINDEX 会改写)。

1. $PGDATA 基目录(数据区根)

由 initdb -D /path/to/pgdata 初始化,postgresql.conf 里 data_directory 指向它。主要内容:

条目作用
postgresql.conf主配置(端口、内存、WAL、并行、日志等);也可放配置目录 conf.d/ 或外部路径
pg_hba.conf客户端认证规则(host/local、md5/scram、IP 白名单)
pg_ident.conf操作系统用户 → 数据库用户映射
postmaster.opts记录上次启动命令行
postmaster.pid单实例锁文件 + PID,防止重复启动
global/聚簇共享系统表(见 §2)
base/各数据库本体(见 §3)
pg_wal/预写日志 WAL(见 §6)
pg_tblspc/表空间软链(见 §5)
pg_logical/逻辑复制槽、解码快照
pg_stat/持久化的统计计数器(pg_stat_* 视图来源)
pg_commit_ts/事务提交时间戳(开启 track_commit_timestamp)
pg_notify/LISTEN/NOTIFY 队列文件
pg_serial/可序列化隔离事务信息
pg_subtrans/子事务(subtransaction)状态
pg_multixact/多事务(多行被并发锁)状态
pg_snapshots/导出的快照(pg_export_snapshot)
log/服务器日志(logging collector 输出,log_directory 控制)

⚠️ 物理备份/复制的最小集合:$PGDATA 全量 + 持续归档的 WAL(pg_wal 或 archive)。直接拷贝数据文件做一致性备份必须配合 pg_start_backup()/pg_stop_backup() 或用 pg_basebackup,否则会裂。


2. global/ 聚簇级共享系统目录

存放整个聚簇只有一份的系统 catalog,不隶属于任何单个 database:

  • pg_database:聚簇里所有数据库的清单(OID、encoding、归并序、owner)——所以你连上任意库都能 SELECT datname FROM pg_database。
  • pg_tablespace、pg_control(控制文件二进制,记录系统检查点、RECV 起点、TLI 时间线)、pg_filenode.map、pg_internal.init 等。
  • pg_control 内容用 pg_controldata $PGDATA 查看(系统状态、checkpoint WAL 位置、 WAL 段大小、页大小)。

对应课程图里 global/ 那一摞文件:它是”聚簇元数据中枢”。


3. base/ —— 每个数据库一个 OID 子目录

base/ 下每个子目录名就是数据库的 OID(oid 列,见 SELECT oid, datname FROM pg_database;):

目录名对应库说明
1/template0模板库(不可连接,作为建库模板)
12891/template1默认建库模板(CREATE DATABASE 不带 TEMPLATE 时用它)
12896/postgres默认维护库
16384/sampledb用户自建库(OID 通常 ≥ 16384)
16385/testdb用户自建库

进入某个库目录后,看到的是该库所有对象的物理文件,文件名 = relfilenode:

  • 表的数据文件、索引文件、TOAST 表、序列、系统 catalog 全在这里;
  • 名字是一串数字,不直接叫表名,要反查:-- 查表 sampletbl 的 relfilenode SELECT relname, relfilenode, reltablespace FROM pg_class WHERE relname = 'sampletbl';课程图里 18740 (sampletbl) 就是这个 relfilenode。

4. 表/索引的物理文件:主文件 + FSM + VM + init fork

一张表(堆表)在磁盘上通常对应同一个 relfilenode 的多个 fork(分叉文件):

文件名fork作用
18740main堆数据文件本体,按 8KB 页(page)组织
18740_fsmfsmFree Space Map:记录每个页还有多少空闲空间,插入/UPDATE 找可用页时用
18740_vmvmVisibility Map:哪些页对所有事务可见(全可见位),加速 index-only scan、VACUUM 跳过全可见页
18740_initinit仅 unlogged 表:崩溃重建时用它初始化

关键点:

  1. 页大小默认 8KB(BLCKSZ,编译时定,见 pg_controldata)。
  2. 单文件超过 1GB(默认)自动切段:18740、18740.1、18740.2……这是历史上应对 2GB/32 位文件限制的设计,段号从 1 开始。
  3. 索引文件:每个索引也有自己的 relfilenode(同样的数字名),B-tree 索引内部也是页结构;REINDEX 会生成新 relfilenode 再切换。
  4. TOAST 机制:行宽超过约 2KB(TOAST_TUPLE_THRESHOLD)时,大字段被压缩/外存到独立的 TOAST 表(pg_toast_<表OID>),主表只存指针。
  5. FSM 不会被 VACUUM 自动收缩成 0:它是密度映射,空闲信息靠 VACUUM 回收后更新。
  6. VM 与 Hint Bits:第一次全表扫描时会给 VM 打”全可见”标记,这就是为什么大表冷启动第一次扫描会产生 xlog/脏页。

5. 表空间(Tablespace)与 pg_tblspc/

课程图右侧那块绿色 tablespace,物理上就是:

  • CREATE TABLESPACE ts LOCATION '/data/pg_ts';
  • 在 $PGDATA/pg_tblspc/ 下生成一个以表空间 OID 命名的符号链接(Windows 上是 junction/copy),指向 /data/pg_ts;
  • 表空间目录里再按 PG_VERSION/版本/数据库OID/ 组织对象文件。

要点:

  • 表空间只是”换个目录放文件”,不改变 cluster 只有一个 PGDATA 的事实,pg_tblspc 是入口。
  • CREATE TABLE ... TABLESPACE ts; 时,该表的 relfilenode 文件落在表空间路径下,pg_class.reltablespace 记录它。
  • 表空间级别可以 CREATE DATABASE ... TABLESPACE 让整库落到自定义盘。
  • 备份/克隆时不能只拷 PGDATA,表空间是软链,要连同实际目录一起恢复,否则起不来。

6. pg_wal/ —— 预写日志(Write-Ahead Logging)

  • 旧称 pg_xlog(9.6 之前叫 xlog,10 起改名 pg_wal)。
  • 每个 WAL 段默认 16MB(--wal-segsize 编译/initdb 时定),文件名是 24 位十六进制:00000001000000000000000A。
  • 作用:先日志后数据页,保证崩溃可恢复(ARIES 风格)、支持流复制、PITR 时间点恢复。
  • 相关参数:wal_level(replica/logical)、max_wal_size、min_wal_size、checkpoint_timeout、archive_mode+archive_command(归档)。
  • 物理架构上它和数据页分离目录,可以把 pg_wal 单独挂高速盘,降低写竞争。

7. 后台进程与内存(物理运行时视图)

物理存储之外,”一个实例在跑”时还包括进程和共享内存:

主要后台进程:

  • postmaster:守门进程,监听端口、fork/回收 backend。
  • checkpointer:执行 checkpoint,把脏页刷盘并推进 REDO 起点。
  • bgwriter:主动批量刷脏页,降低 checkpoint 突发 IO。
  • walwriter:把 WAL 缓冲区写 pg_wal。
  • walsender / walreceiver:主从流复制发送/接收。
  • autovacuum launcher / worker:自动 dead tuple 清理与表统计更新。
  • logical replication launcher、stats collector(新版改为 pg_stat 进程)等。

共享内存(Shared Memory):

  • shared_buffers:堆表页缓存(默认 128MB,生产建议物理内存 25% 左右)。
  • WAL 缓冲区、commit log(clog,提交状态)、lock manager、disk quorum 等。

8. 常用排查命令(对照物理架构)

# 1. 看数据目录结构与控制信息
pg_controldata $PGDATA
ls -l $PGDATA

# 2. 找某个库的 OID → 对应 base 目录
psql -c "SELECT oid, datname FROM pg_database;"

# 3. 找某张表的物理文件
psql -c "SELECT relname, relfilenode FROM pg_class WHERE relname='sampletbl';"
# 然后去 base/<dbOID>/ 或对应表空间目录下找 relfilenode 文件

# 4. 看表空间实际路径
psql -c "\db+"
ls -l $PGDATA/pg_tblspc/

# 5. 看表大小(含 FSM/VM/TOAST)
psql -c "\dt+ sampletbl"
psql -c "SELECT pg_size_pretty(pg_total_relation_size('sampletbl'));"

# 6. WAL 状态
psql -c "SELECT * FROM pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0');"
ls -lh $PGDATA/pg_wal/

9. 与 Oracle / MySQL 的对照(帮助记忆)

维度PostgreSQLOracleMySQL(InnoDB)
实例与库一个实例一个 PGDATA,多库物理隔离一个实例多表空间,库=schema一个实例多库,库间可访问
数据根$PGDATA/base/<dbOID>/各表空间 .dbfdatadir/<db名>/
表文件relfilenode 数字文件 + FSM/VM段/区/块,统一在表空间每库每表 .ibd
预写日志pg_wal(WAL)redo logib_logfile
回滚undo(元组版本+vacuum,无独立 undo 表)undo 表空间undo log
复制流复制(WAL 字节)Data Guard / redobinlog / relay log

10. WAL 与 Checkpoint 流程(ARIES 恢复原理)

10.1 WAL 记录长什么样

  • WAL 是逻辑+物理混合的重做日志:记录”对某页某个偏移做了什么修改”(redo),并在 commit 时刻写一条 commit 记录。
  • 写入路径:backend 改共享缓冲页 → 先把 WAL 记录写进 WAL 缓冲区 → walwriter 按 wal_writer_delay(默认 200ms)或 commit 时 fsync 落 pg_wal/。
  • WAL 先于数据页落盘(WAL 永不落后于脏页刷盘),这是崩溃恢复成立的前提。

10.2 LSN 与 WAL 段

  • 每条 WAL 有唯一 LSN(Log Sequence Number),8 字节逻辑偏移,显示成 0/16B2D48。
  • 段文件命名:TLI(时间线,8位) + 逻辑高32位(8位) + 低32位(8位),默认每段 16MB。
  • 三个关键”位点”:
    • redo:上一次 checkpoint 开始的重做起点(pg_control 里)。
    • insert:当前已写入的 WAL 末尾。
    • recycle/remove:旧段在 checkpoint 后可被循环复用(max_wal_size 控制上限)。

10.3 Checkpoint 做了什么

触发条件:checkpoint_timeout(默认 5min)、WAL 量到 max_wal_size、手动 CHECKPOINT;、关机/Promote。

一次 checkpoint 顺序:

  1. 写 checkpoint 记录到 WAL(记录当前 redo 起点)。
  2. 把 shared_buffers 里所有脏页批量刷到数据文件(由 bgwriter 平时渐进刷,checkpoint 负责兜底)。
  3. 刷 clog(事务提交状态)、依赖文件。
  4. 把新 redo 起点写回 pg_control。
  5. 标记旧 WAL 段可回收/归档。

关键参数(生产调优):

参数默认说明
shared_buffers128MB建议物理内存 25%(Linux 注意 transparent_hugepage)
wal_buffers-1(≈ shared_buffers 的 1/32,≈16MB)高并发写可调到 64MB
checkpoint_timeout5min调到 10~15min 减少刷盘抖动
max_wal_size1GB调到 4~16GB,给 checkpoint 留余量、避免频繁 recycle
min_wal_size80MB保持循环复用区,避免 WAL 频繁扩缩
checkpoint_completion_target0.9把刷脏页摊到整个 checkpoint 周期,削峰
wal_buffers/synchronous_commiton高可用可保留 on;极致性能可权衡 off/remote_apply

10.4 崩溃恢复(Startup 进程)

实例拉起时:

  1. 读 pg_control,拿到上次 checkpoint 的 redo 起点 checkpointRedo。
  2. 从该 LSN 重放(redo)所有 WAL:把当时被改过的页重新应用一遍——即使数据页已经脏写过也没关系(幂等重放)。
  3. 处理 commit/abort:已 commit 的事务生效,未 commit 的回滚(undo 靠 tuple 版本标记,不是独立 undo 段)。
  4. 恢复到一致状态后开始接受连接。

11. VM / FSM 与 VACUUM 机制(物理文件如何被”维护”)

11.1 为什么需要 VACUUM

PG 是 MVCC 多版本:UPDATE/DELETE 并不原地删旧元组,而是留 dead tuple(死亡版本),靠 xmin/xmax 事务号判断可见性。dead tuple 会膨胀、占空间、拖慢扫描,必须回收——这就是 VACUUM。

11.2 Visibility Map(VM,_vm)

  • 每 8KB 页对应 VM 里一个 bit,标记”该页对所有当前及未来事务都可见(没有任何 dead tuple、全可见)”。
  • 作用:
    • Index-Only Scan:索引回表时若 VM 标记页全可见,就不用再回堆表取可见性。
    • VACUUM 跳过全可见页:autovacuum 直接跳过 VM 置位的页,大幅省 IO。
  • VM 由第一次全表扫描时的 hint-bit 写和 VACUUM 共同维护。

11.3 Free Space Map(FSM,_fsm)

  • 记录每个页还剩多少可用空间(按二叉树密度组织)。
  • 作用:INSERT/UPDATE 找”还有空位的页”时,直接查 FSM,而不是顺序扫描整个表。
  • FSM 不自动收缩成 0;VACUUM 回收 dead tuple 后更新空闲信息;VACUUM FULL/CLUSTER 重写物理文件才真正缩小尺寸。

11.4 VACUUM 家族

命令做什么锁是否收缩文件
VACUUM标记 dead tuple 可复用、更新 FSM、统计信息不阻塞 DML(share update exclusive)否,文件不变小
VACUUM FULL整表重写、压缩、重建索引排他锁(AccessExclusive),阻塞读写是,文件物理缩小
VACUUM ANALYZEVACUUM + 采样更新统计信息(优化器用)同 VACUUM否
VACUUM (INDEX_CLEANUP)控制是否清理死亡索引元组同 VACUUM否

11.5 autovacuum 与 XID 回卷(Wraparound)

  • PG 事务 ID 是 32 位计数器,用完会强制 autovacuum(防止 XID 回卷覆盖仍可见的旧元组)。
  • 关键监控:age(datfrozenxid) 接近 autovacuum_freeze_max_age(默认 2 亿)时会触发激进 freeze。
  • 常用排查:SELECT datname, age(datfrozenxid) FROM pg_database ORDER BY 2 DESC; SELECT relname, n_dead_tup, n_live_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;

12. PostgreSQL 17.11 编译参数调优(BLCKSZ / WAL 段大小)

适用:你从源码编译部署(之前记录过 17.11 编译排错)。这些参数在 initdb 时定,事后不可改(改了要整库重建),所以必须在初始化前规划。

12.1 页大小 BLCKSZ(--block-size)

  • 默认 8KB。
  • 编译选项:./configure --block-size=8(单位 KB),可设 4/8/16/32。
  • 怎么选:
    • OLTP 小事务:保持 8KB 最稳妥,缓存命中、WAL 放大均衡。
    • 数仓/宽行/大 JSON:16KB 可减少跨页行、提升顺序扫描;但单行若超过页大小 1/4 会走 TOAST,且 WAL 放大更明显。
    • 4KB:适合极度随机小 IO、flash 对齐,但行效率低,一般不选。
  • ⚠️ 改 BLCKSZ 是集群级不可逆,pg_controldata 里能看到 Block size。

12.2 WAL 段大小(--wal-segsize)

  • 单位 MB,默认 16,范围 1~1024(1GB)。
  • 编译/initdb:./configure --with-wal-segsize=16,或 initdb --wal-segsize=64。
  • 怎么选:
    • 高写入/大库:64~256MB,减少段文件数量、降低 checkpoint 时段循环/归档的开销(注意 max_wal_size 要相应放大,否则段数太少达不到缓冲)。
    • PITR 频繁归档:段太大单次归档块大,恢复粒度粗;段太多则 pg_wal 目录文件数爆炸。
  • 生产常见组合:--block-size=8 --wal-segsize=64,配合 max_wal_size=8GB。

12.3 其他编译期可调项(相关)

选项说明
--with-blocksize / --with-wal-blocksize表页 / WAL 页大小(WAL 页默认 8KB,一般不动)
--enable-debug / --enable-cassert调试版,生产别开
--with-ssl=opensslOpenSSL 支持
--with-python/--perl/--libxml扩展语言支持
CC='gcc' CFLAGS='-O2 -march=x86-64-v3'按 CPU 架构优化(你之前 17.11 排错时依赖)

12.4 初始化后核对

# 编译/初始化后确认页大小与 WAL 段
pg_controldata $PGDATA | findstr /I "block size WAL segment"
# 输出示例:
#   Data page size                       8192
#   WAL block size                       8192
#   WAL segment size                     67108864   (64MB)

13. PostgreSQL 18.x 物理架构相关变更(18.0 大版本 + 18.6 维护版)

你当前编译的是 17.11,这里把升到 18.x 时物理/存储层要知道的变化单独列出。
注意:18.6 本身是维护版本(2026-08-13),不引入新特性;真正的物理层大改来自 18.0(2025-09-25)。

13.1 18.0 大版本:AIO 异步 I/O 子系统(物理运行时最大变化)

  • 引入全新的 Asynchronous I/O (AIO) 子系统,backend 可以一次性并发提交多个读请求,而不是逐个阻塞等待。
  • 受益操作:顺序扫描(seq scan)、位图堆扫描(bitmap heap scan)、VACUUM 等,官方基准从存储读取性能最高提升约 3 倍。
  • 相关 GUC(18.0 新增): 参数 作用 io_method 选择 I/O 方式(sync / io_uring 等,Linux 下建议 io_uring) io_combine_limit 单次合并 I/O 请求上限 io_max_combine_limit 合并上限的硬顶
  • 其他大版本相关:pg_upgrade 现在保留优化器统计信息(升级后不用立即全库 ANALYZE);支持多列 B-tree 的 skip scan。
  • 编译侧:18.x 建议用较新 Linux 内核(io_uring 需 kernel ≥ 5.1,推荐 5.15+),否则自动退回 sync。

13.2 18.6 维护版:与物理架构直接相关的关键修复

这些修复都直接改在本笔记讲过的机制上,升级到 18.6 时建议重点关注。

修复点对应笔记章节影响
VM 位图清除位的 WAL 记录被遗漏(WAL summarizer 没记录这类 VM 变更)§10 WAL / §11 VM增量备份(incremental backup)可能基于错误的 VM 状态;且这类 VM 页在需要时没写全页镜像(FPI),torn page 无法被 redo 纠正,可能导致 index-only scan 返回错误结果
VACUUM wraparound failsafe 模式下恢复共享缓冲池完整使用§11.5 回卷v18 重构时误把 failsafe 模式也限制在少量 shared buffer,导致防回卷 freeze 变慢;18.6 恢复”紧急时不限 buffer”的原设计
批量扩展表时最后一个块未及时写入 FSM(off-by-one)§4 FSM多块扩展的表最后一个块长期不被 FSM 标记为空闲,空间暂时浪费,直到 VACUUM 才纠正
autovacuum 处理数据库的顺序颠倒§11.5 autovacuum原本应按分数从高到低挑库,18.x 误成从低到高,18.6 纠正
并行 VACUUM worker 进度报告内存泄漏(每次报告漏 ~1kB)§11.4 VACUUM长时间运行的并行 vacuum worker 进程内存缓慢累积
临时表访问限制 local buffer 数量§7 共享内存之前 effective_io_concurrency 过大时,单条读流可能耗尽所有本地 buffer,报”no empty local buffer available”;18.6 加了上限
GIN 并行索引构建错误更新 pg_class.reltuples(可能出现 Infinity/NaN)§11.5 统计会导致 autovacuum / autoanalyze 错误地判断该表不需要处理,且不自愈;升级后需对含 GIN 索引的表手动 ANALYZE 或重建索引
WAL summarizer 在 timeline switch(主备切换)时卡住§10.2 LSN/TLI备机提升时间线后 summarizer 进程挂起,18.6 修复

13.3 18.6 安全修复(升级建议,与物理层无关但必须打)

  • 新增 GUC output_plugin_libraries:限制逻辑解码可加载的输出插件(默认只放行 pgoutput、test_decoding),第三方插件需手动加入。
  • 主要 CVE:CVE-2026-14662(tsvector/tsquery 整数回绕)、CVE-2026-18024(ascii() 越界读)、CVE-2026-18408(pg_dump 恢复时命令注入)、CVE-2026-19385(pg_dump 内存越界)、CVE-2026-14676(pg_stat_statements 缓冲越界)等。
  • 扩展重建建议:装完 18.6 后,若用了 btree_gist(float/bit 列、!= 操作)或 ltree(>14653 标签),建议 REINDEX 相关索引。
  • 构建侧:支持 OpenSSL 4。

13.4 从 17.11 升到 18.6 的物理层注意清单

  • 内核版本:生产启 io_uring 需 kernel ≥ 5.1(建议 5.15+),否则保持 io_method=sync。
  • 升级后检查含 GIN 索引表的 pg_class.reltuples,异常就 ANALYZE。
  • 用了 btree_gist/ltree 的库升级后 REINDEX。
  • 逻辑解码用了第三方输出插件的,在 output_plugin_libraries 里显式放行。
  • 依赖增量备份(pg_combinebackup / WAL summarizer)的环境,强烈建议升到 18.6(VM 漏记是数据正确性 bug)。
  • pg_upgrade 后统计信息保留,不必立即全库 ANALYZE,但大表首次查询前可按需手工收集。

14. 从 17.11 原地升级到 18.6(pg_upgrade 物理操作步骤)

适用:同一台机器、pg_upgrade 二进制原地替换数据目录(不 dump/restore)。
前提:大版本跨号升级(17→18),小版本(18.4→18.6)直接替换二进制即可,不需要 pg_upgrade。

14.1 升级前检查清单(必做)

  • 确认大版本兼容:17→18 支持原地升级;17.x 的小版本必须 ≥ 最低要求(建议 17.4+,你是 17.11 满足)。
  • 架构/编译选项必须一致:
    • 相同字节序、相同 --block-size(BLCKSZ)、相同 --wal-segsize。
    • 相同 --integer-datetimes(18 已强制开启,17 默认也是,一致)。
    • 相同安装架构(32/64 位)。
  • 备份:pg_dumpall 逻辑备份 + pg_basebackup 物理备份(或 tar 冷备整个 $PGDATA 和所有表空间目录)。
  • 停应用:确认无业务连接。
  • 检查扩展:记录所有 CREATE EXTENSION,升级后逐个 ALTER EXTENSION ... UPDATE;特别是 18.6 提到的 btree_gist、ltree、pg_trgm、fuzzystrmatch。
  • 检查 64 位时间戳:18 只支持 64 位 timestamp(17 默认已开启,无问题)。
  • 磁盘空间:新 18.6 安装目录 + 旧数据目录保持不动;pg_upgrade 不复制整库,但需要足够临时空间写日志。

14.2 部署 18.6 二进制

# 假设编译安装目录约定
# 旧版 17.11:/usr/local/pgsql/17
# 新版 18.6 :/usr/local/pgsql/18
cd /opt/src/postgresql-18.6
./configure --prefix=/usr/local/pgsql/18 \
  --block-size=8 \
  --wal-segsize=16 \
  --with-openssl --with-python --with-libxml \
  CC='gcc' CFLAGS='-O2 -march=x86-64-v3'
make -j$(nproc)
make install
# 编译 contrib(你用到的扩展)
cd contrib && make && make install

⚠️ 不要让新旧二进制共用 bin/、lib/、share/ 目录,必须分目录。

14.3 停旧库、做冷备

# 停 17.11
systemctl stop postgresql-17
# 或 su - postgres -c '/usr/local/pgsql/17/bin/pg_ctl -D /pgdata/17 stop -m fast'

# 物理冷备(关键:保留回滚路径)
tar -czf /backup/pgdata-17-$(date +%F).tar.gz /pgdata/17
# 表空间目录也要打包(pg_tblspc 是软链,实际目录在外面)
tar -czf /backup/tblspc-17-$(date +%F).tar.gz /data/pg_ts/

14.4 执行 pg_upgrade(核心)

# 用 postgres 用户,建议新建一个空工作目录跑
mkdir -p /tmp/pg_upgrade && cd /tmp/pg_upgrade

/usr/local/pgsql/18/bin/pg_upgrade \
  --old-datadir /pgdata/17 \
  --new-datadir /pgdata/18-init \
  --old-bindir /usr/local/pgsql/17/bin \
  --new-bindir /usr/local/pgsql/18/bin \
  --jobs $(nproc) \
  --check          # 第一步先加 --check 只做预检,不改动

预检通过后去掉 --check 正式跑:

# 先 initdb 一个新空目录(pg_upgrade 不会自动建)
/usr/local/pgsql/18/bin/initdb -D /pgdata/18-init \
  --encoding=UTF8 --locale=C \
  --block-size=8 --wal-segsize=16 \
  -U postgres

# 正式升级
/usr/local/pgsql/18/bin/pg_upgrade \
  --old-datadir /pgdata/17 \
  --new-datadir /pgdata/18-init \
  --old-bindir /usr/local/pgsql/17/bin \
  --new-bindir /usr/local/pgsql/18/bin \
  --jobs $(nproc) \
  --link            # ← 关键:硬链接模式,秒级完成;不复制数据文件

--link vs 缺省:

  • --link:新旧数据文件硬链接共享同一个 inode,升级快、不占额外空间;但一旦 link 后不能再回退到 17(数据已被新格式改写元数据)。生产推荐先备份再用 --link。
  • 不带 --link:完整复制数据文件,耗时长、占双倍空间;优点是旧目录保持原样可回退。

14.5 升级后的物理文件变化

pg_upgrade 完成后:

  • 新 $PGDATA(/pgdata/18-init)里:
    • global/、base/、pg_wal/ 等目录已被切换成 18 格式;
    • 用 --link 时,base/<dbOID>/ 下的 relfilenode 数据文件和旧目录是同一 inode(硬链接);
    • pg_control 已更新为 18 格式;
    • postgresql.conf 从旧配置复制过来(需手动补 18 新增参数)。
  • 表空间:pg_tblspc/ 下的软链指向实际目录,目录里多了一个 PG_18_xxx/ 子目录(按版本+DB OID 组织)。
  • 旧目录 /pgdata/17 在 --link 模式下不要立刻删,确认新库跑稳后再删(delete_old_cluster.sh 脚本会自动生成)。

14.6 启动新库并收尾

# 1. 先把配置文件对齐
cp /pgdata/17/postgresql.conf /pgdata/18-init/   # 或手工合并,补 io_method 等新参数
cp /pgdata/17/pg_hba.conf /pgdata/18-init/

# 2. 启动 18.6
/usr/local/pgsql/18/bin/pg_ctl -D /pgdata/18-init -l /tmp/pg18.log start

# 3. 按 pg_upgrade 屏幕提示跑生成的脚本
#    它会:(a) 升级统计信息 (b) 迁移 froze 状态
./analyze_new_cluster.sh      # 18 起 pg_upgrade 保留统计,这步主要做收尾
./delete_old_cluster.sh       # 确认无误后再跑,删除旧 17 数据

必须手动做的收尾 SQL:

-- 1. 升级所有扩展
\c postgres
SELECT extname, extversion FROM pg_extension;
ALTER EXTENSION plpgsql UPDATE;
ALTER EXTENSION btree_gist UPDATE;
ALTER EXTENSION ltree UPDATE;
ALTER EXTENSION pg_trgm UPDATE;
ALTER EXTENSION pg_stat_statements UPDATE;
-- 其他扩展同理

-- 2. 检查 GIN 表 reltuples(18.6 修复项)
SELECT relname, reltuples FROM pg_class
WHERE relkind='r' AND reltuples < 0 OR reltuples = 'Infinity'::float;
-- 异常就 ANALYZE 对应表

-- 3. 配置 18 新增 AIO(在 postgresql.conf)
-- io_method = io_uring          # Linux 内核 ≥5.15 建议开
-- io_combine_limit = 128

14.7 升级后验证清单

# 版本与控制文件
/usr/local/pgsql/18/bin/pg_controldata /pgdata/18-init | grep -E "Version|cluster"
psql -c "SELECT version();"
psql -c "SHOW server_version;"

# 关键对象数量核对(升级前后应一致)
psql -c "SELECT count(*) FROM pg_class WHERE relkind='r';"
psql -c "SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database;"

# 表空间路径
psql -c "\db+"
ls -l /pgdata/18-init/pg_tblspc/

# AIO 是否生效(18 新参数)
psql -c "SHOW io_method;"
psql -c "SHOW io_combine_limit;"

14.8 回滚方案(没用 --link 才可行)

  • 没用 --link:直接停 18,用旧的 /pgdata/17 启动 17 二进制即可。
  • 用了 --link:数据文件已被 18 改写元数据,不能回退到 17;只能靠冷备 tar 包恢复,或从 18 库 pg_dumpall 后重建 17。
  • 建议:生产环境先用一台同版本测试库演练,确认无误再上生产;--link 模式前必须确认备份可用。

15. 一句话总结

PostgreSQL 物理架构 = 一个 $PGDATA 数据区,里面用 base/<dbOID>/ 把每个库物理隔开;库里每张表用一个数字 relfilenode 文件存堆数据,并配一个 FSM(空闲映射)和一个 VM(可见性映射);global/ 管聚簇元数据,pg_wal/ 靠 WAL→checkpoint→崩溃恢复支撑持久性与流复制,pg_tblspc/ 用软链把表/对象放到自定义磁盘路径。页大小与 WAL 段在编译/initdb 时定死。18.0 起新增 AIO 异步 I/O 子系统(io_method=io_uring)提升扫描/vacuum 吞吐;18.6 则修复了 VM 漏记 WAL、FSM off-by-one、vacuum failsafe buffer 限制等物理层正确性问题。跨大版本升级用 pg_upgrade --link 硬链接模式秒级完成,但需先冷备、编译选项一致、升级后扩展逐个 UPDATE。

赞(0) 打赏
未经允许不得转载:徐万新之路 » PostgreSQL 物理架构

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

联系我们

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

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

支付宝扫一扫

微信扫一扫

登录

找回密码

注册