PostgreSQL 内核机制 — 系列规划
本文是写作规划,不是可发布正文。按
WRITING_GUIDE.md第十三节流程:先定来源台账、实验台账、篇目结构,再写 index.md 和正文。
一、为什么写这个系列
1.1 现有内容的覆盖情况
博客已有的数据库/存储相关内容:
| 已有系列 | 视角 | 与 PG 内核的重叠 |
|---|---|---|
| 存储工程 (79) | 通用存储介质、文件系统、存储引擎概览 | 讲到 B-Tree、Buffer Pool、WAL/ARIES 的通用原理,但不到 PG 具体实现 |
| db-frontier (25) | 顶会前沿(Learned Index、向量检索、HTAP、TEE) | 不涉及 PG 内核实现 |
| db/LSM-Tree (9) | 从零实现 LSM 引擎 | B-Tree 体系,与 PG 无关 |
| db/mvcc (1) | PG 的 MVCC、快照隔离、SSI | 唯一一篇深入 PG 源码的文章,但只覆盖并发控制一个维度 |
| 分布式 (70) | Paxos/Raft、一致性、复制 | 复制理论有覆盖,但不涉及 PG 流复制的具体实现 |
| OS (111) | 进程、内存、文件系统、同步原语 | 理论基础,不涉及 PG 如何使用这些机制 |
结论:博客有深厚的存储和并发理论基础,但缺少对一个完整生产级数据库内核的系统性源码拆解。PG 是开源关系型数据库的标杆,其源码和文档质量极高,天然适合这个角色。
1.2 为什么选 PostgreSQL(而不是 MySQL InnoDB)
| 维度 | PostgreSQL | MySQL/InnoDB |
|---|---|---|
| 源码可读性 | 高,注释详尽,README 遍布关键目录 | 中等,历史包袱重 |
| 文档质量 | 官方手册即是事实标准,每个 GUC 有详细说明 | 官方文档覆盖面窄,依赖社区 |
| 学术关联 | PG 论文传统强(Joseph Hellerstein、Michael Stonebraker 等) | 论文少 |
| 扩展性 | Hook 机制丰富,FDW 清晰 | 插件机制受限 |
| 社区讨论质量 | pgsql-hackers 邮件列表归档详尽 | 内部化,公开讨论少 |
| 与博客已有系列联动 | MVCC 文章已用 PG 源码,存储工程通用原理可落地 | InnoDB 没有既有文章铺垫 |
1.3 系列定位
两条腿走路:内核机制拆解 + 生产运维实战。
上半场(第 1–20 章)是 从进程模型到磁盘页面的完整内核拆解:每篇文章盯住一个内核子系统,从源码路径钉住结论。下半场(第 21–26 章)是 从内核知识到故障现场的反向应用:用内核机制解释真实生产事故的根因、排查路径和修复边界。
具体来说:
内核拆解线(原 20 章): - 对着 PG 源码追踪一条 SQL 的完整执行路径 - 理解每个 GUC 参数背后的内核行为 - 在遇到性能问题或异常行为时,知道从哪个内核模块入手排查
运维实战线(新增 6 章): -
识别经典故障模式的根因——连接风暴、wraparound 危机、OOM 连锁
kill、replication slot 溢出——而不只是跟着 runbook 操作 - 用
pg_stat_activity + wait_event +
pg_locks + kernel trace 四层工具链定位性能异常
- 理解 pg_upgrade、PITR、pg_resetwal
的内部机制和操作边界——知道什么情况该用什么、什么情况绝对不能碰
- 掌握 PG
配置项之间的联动关系和隐性陷阱——work_mem
不只是”每个操作的内存上限”、fsync=off
为什么诱惑巨大但可能让你丢数据 -
从他人事故中学习模式而非个案——每条”常见坑”都配内核机制解释和可复现的验证方法
二、核心问题与目标读者
2.1 九个关键问题
PG 的进程模型多进程共享内存到底是怎么工作的? Postmaster、backend、background worker、autovacuum launcher/worker — 这些进程如何协作?共享内存中有哪些关键数据结构?
fork()的开销 PG 怎么消化? → 第 1、2 章回答。一条 SQL 从客户端到磁盘经历了哪些环节? Parser → Analyzer → Rewriter → Planner → Executor → Buffer Manager → WAL → Disk。每个环节的输入输出、关键数据结构和性能边界是什么? → 第三部分(第 9-13 章)逐环节拆解。
PG 的 MVCC 为什么需要 VACUUM?膨胀到底怎么发生的? 已有 mvcc 文章讲了可见性判断,但没深入讲:CLOG 的结构、hint bit 的写入时机、VACUUM 的内部流程、freezing 的必要性、膨胀的量化分析。 → 第 3、8 章 + 扩展已有文章。
PG 的查询优化器如何做决策?统计信息不准的时候怎么办?
pg_statistic里存了什么?选择率估算公式从哪来?Join 顺序搜索空间多大?GEQO 什么时候接管?Extension 怎么影响 plan? → 第 10、11 章。PG 的高可用方案(流复制、逻辑复制、逻辑解码)各自的机制边界在哪? WAL Sender/Receiver 的协议细节、同步复制 vs 异步复制的语义差异、逻辑解码的 reorder buffer、逻辑复制的冲突处理。 → 第 18、19 章。
PG 最危险的 10 种故障模式——根因、排查路径和修复边界是什么? 连接风暴为什么能打垮 PG(哪怕
max_connections设了上限)?事务 ID wraparound 在pg_stat_activity里最早期的信号是什么?replication slot 溢出的多米诺效应——一个 slot 如何拖垮整个集群? → 第 22 章集中回答,各机制章节点出前置知识。排查慢查询的完整工具链怎么用——如何从现象定位到内核根因? 从
pg_stat_statements定位高耗时 queryid →EXPLAIN (ANALYZE, BUFFERS)看实际行数和 buffer 访问 →pg_stat_activity+wait_event诊断等什么锁或 I/O →perf/bpftrace追踪内核调用栈。不是一个命令,而是一条调查链。 → 第 23 章集中回答,第 6、10、12 章提供排查所需的内核知识。PG 最容易被误解和配错的 15 个 GUC——配错之后会发生什么?
work_mem不只是”每个操作的内存上限”——当 200 个连接同时做 hash join 时会发生什么?fsync=off为什么诱惑巨大但COMMIT之后数据真可能丢?huge_pages=on在 Docker 里为什么静默退化?random_page_cost在 NVMe 上的默认值有多离谱? → 第 26 章回答,第 5、10、18 章提供内核基础。pg_upgrade / PITR / pg_resetwal 的内部机制和操作边界是什么——什么时候该用、什么时候绝对不能碰? pg_upgrade
--link的原理和崩溃恢复风险、PITR 恢复的”正确时间点”怎么定、pg_resetwal为什么是最后手段——以及用了之后哪些数据不可访问。 → 第 24、25 章回答。
2.2 目标读者
| 维度 | 说明 |
|---|---|
| 主要读者 | 数据库内核开发者、后端工程师(日常使用 PG 且想深入理解)、SRE/DBRE(需要内核视角排查问题) |
| 次要读者 | 系统软件方向学生、从 MySQL 转 PG 的工程师、分布式数据库开发者(参考 PG 的单机设计) |
| 先修知识 | SQL、基本数据库概念(ACID、索引、WAL)、C 语言阅读能力、操作系统基础(进程、内存、I/O) |
| 不假设 | PG 内核贡献经验、数据库理论论文深度 |
三、篇目结构
全系列 20 章,分五部分。每章按”问题 — 源码 — 实验 — 边界”组织。
第一部分:体系结构地基(第 1-2 章)
第 1 章:进程模型与共享内存
- Postmaster 的启动流程与信号处理
- Backend 进程的生命周期:
fork()→ InitPostgres → 主循环 → 退出 - 共享内存初始化:
CreateSharedMemoryAndSemaphores()的内部流程 - 关键共享内存结构:
PGPROC、ProcArray、PGXACT、BufferDescriptors、LWLock数组 - Background worker 注册与调度
- 源码焦点文件:
src/backend/postmaster/postmaster.c、src/backend/storage/ipc/shmem.c、src/backend/storage/ipc/procarray.c
第 2 章:页面布局与元组格式
- Page 物理布局(
PageHeaderData、ItemId 数组、special space) - Heap tuple
格式(
HeapTupleHeaderData:xmin/xmax、t_ctid、t_infomask2/t_infomask) - TOAST 机制:压缩阈值、外存表、四种策略
- 大字段的存储路径:inline → toasted → external
- 实验:
pageinspect扩展直接观察页面字节 - 源码焦点文件:
src/include/storage/bufpage.h、src/include/access/htup_details.h、src/backend/access/heap/heaptoast.c
第二部分:并发控制与崩溃恢复(第 3-8 章)
第 3 章:MVCC 实现(扩展已有文章)
- 复习 xmin/xmax + 快照 (
GetSnapshotData) 的获取流程 - 本文新增核心内容:
- CLOG(提交日志)的内部结构:SLRU 页面、事务状态位 (IN_PROGRESS / COMMITTED / ABORTED / SUB_COMMITTED)
- Hint bit:为什么要在 tuple header 写 hint bit?触发时机
(
HeapTupleSatisfiesMVCC→SetHintBits) - 快照的可扩展性:
ProcArrayLock的争用与 PG 14 的优化 (snapshot scalability) - 事务 ID 回卷(wraparound)的威胁
- 与 InnoDB 的 undo log 方案对比
- 源码焦点文件:
src/backend/access/transam/clog.c、src/backend/utils/time/snapmgr.c、src/backend/access/heap/heapam_visibility.c - 已有文章的利用:直接引用
db/mvcc/mvcc.md作为本系列的前置阅读
第 4 章:WAL 内部机制
- WAL 的插入流程:
XLogInsert()→XLogInsertRecord()→WALInsertLocks分段锁 - WAL record 的物理布局:
XLogRecordheader +XLogRecordBlockHeader+ data - WAL 写入路径:WAL buffer →
wal_sync_method→ 磁盘 - Checkpoint 的完整流程:
CreateCheckPoint()的两阶段(REDO point 确定 → 写 checkpoint record) - REDO 恢复:
StartupXLOG()读取 checkpoint → RMGR 分发 wal_level= minimal / replica / logical 的区别- 运维:Checkpoint 触发的 IO
风暴——
checkpoint_completion_target的内核算法和调优陷阱;max_wal_size设小了 WAL 段疯狂切换的根因;pg_waldump定位问题 WAL record 的实操 - 源码焦点文件:
src/backend/access/transam/xlog.c、src/backend/access/transam/xloginsert.c、src/include/access/xlogrecord.h
第 5 章:Buffer Manager
shared_buffers的内部组织:BufferDescriptors 数组 + buffer pool- Clock sweep 替换算法(
StrategyControl/BufferStrategy) - Buffer 访问固定协议:
ReadBuffer()→ pin → use → unpin - Buffer 状态机:unused → unpinned clean → unpinned dirty → pinned
bgwriter的角色与触发条件- Buffer access strategy:批量扫描
(
BAS_BULKREAD) 与 VACUUM (BAS_VACUUM) 的特殊处理 - 实验:
pg_buffercache扩展,观察 buffer 分布与 clock sweep 行为 - 源码焦点文件:
src/backend/storage/buffer/bufmgr.c、src/backend/storage/buffer/freelist.c
第 6 章:锁管理器
- SpinLock → LWLock → Heavyweight Lock 三层体系
- LWLock 的实现演进:PG 9.4 之前 → PG 9.5+ 的原子操作用法
→ PG 16 的
LWLockWaitListLock优化 - Heavyweight Lock:
LockAcquire()的完整路径,lock hash table,等待队列 - 死锁检测 (
DeadLockCheck()) 的等待图算法 - 行级锁 (FOR UPDATE / FOR SHARE) 与
t_infomask中的 lock bits - 实验:
pg_locks视图,构造并观察锁等待链 - 源码焦点文件:
src/backend/storage/lmgr/lwlock.c、src/backend/storage/lmgr/lock.c、src/backend/storage/lmgr/deadlock.c
第 7 章:事务与子事务
- 事务状态机
(
TransState):TRANS_DEFAULT→TRANS_START→TRANS_INPROGRESS→TRANS_COMMIT/TRANS_ABORT - 子事务 (savepoint) 的实现:
TransactionState栈、SubTransactionId - 2PC (Two-Phase Commit):
PREPARE TRANSACTION的 WAL 记录与状态文件 - 事务 ID 分配 (
AssignTransactionId) 与xidStopLimit/xidWrapLimit - 源码焦点文件:
src/backend/access/transam/xact.c、src/backend/access/transam/twophase.c
第 8 章:VACUUM 与 Freezing
- VACUUM 的完整流程:scan heap → remove dead tuples → update indexes → update FSM/VM
- 可见性映射 (Visibility Map) 的结构与维护
- 空闲空间映射 (Free Space Map) 的结构
Index-Only Scan依赖 VM 的机制- Freezing:什么时候冻结 tuple 的
xmin?
vacuum_freeze_min_age/vacuum_freeze_table_age - Anti-wraparound VACUUM 的触发条件
- Autovacuum:launcher vs worker,触发阈值公式,cost-based delay
- 膨胀量化:
pgstattuple与pg_stat_user_tables.n_dead_tup - 运维/事件:Anti-wraparound VACUUM
的预警信号链——从
n_tup_del→age(relfrozenxid)→ autovacuum 告警 → 强制 autovacuum(autovacuum_freeze_max_age已触发)→ 写阻塞(ForceTransactionId)→ 数据库只读 shutdown 的完整危机时间线。典型陷阱:手动取消 autovacuum 导致 wraparound、大表autovacuum_vacuum_cost_delay调太高导致跟不上 dead tuple 产生速度 - 排查:
age(datfrozenxid)的监控 SQL、n_dead_tup增长速度与last_autovacuum间隔的关系、用pg_stat_progress_vacuum定位当前 VACUUM 瓶颈(在 scan 还是在 index cleanup) - 源码焦点文件:
src/backend/access/heap/vacuumlazy.c、src/backend/commands/vacuum.c、src/backend/postmaster/autovacuum.c
第三部分:查询生命周期(第 9-13 章)
第 9 章:查询解析与重写
- Parser:从 SQL 字符串到
RawStmtparse tree(基于gram.y) - Analyzer:
parse_analyze()的语义分析——名称解析、类型推导、权限检查 - Rewriter:规则系统 (
pg_rewrite) 的工作原理,视图展开 Query结构体:rtable、jointree、targetList各字段的含义- 实验:
debug_print_parse/debug_print_rewritten观察 parse tree - 源码焦点文件:
src/backend/parser/gram.y、src/backend/parser/analyze.c、src/backend/rewrite/rewriteHandler.c
第 10 章:查询规划器 — 统计信息与代价模型
pg_class/pg_statistic里存了什么:stadistinct、stanullfrac、stawidth、stakind(MCV / histogram / correlation)ANALYZE的采样流程与统计收集- 选择率估算:
clauselist_selectivity()→ 各类xxxsel()函数 - 代价常量
(
seq_page_cost、random_page_cost、cpu_tuple_cost等) 的物理意义与调优边界 - 扩展统计 (CREATE STATISTICS):多列相关、函数依赖
- 统计信息不准会导致什么:一个真实糟糕计划的解剖
- 排查/坑:统计信息漂移(stats
drift)的诊断——
n_mod_since_analyze过大、ANALYZE 采样比例在超大表上的精度损失、default_statistics_target对 MCV/histogram 桶数的实际影响。auto_explain+auto_analyze不触发的根因排查(autovacuum_analyze_scale_factor设太大导致 insert-only 表从来不 analyze)。临时表与统计信息的特殊行为 - 源码焦点文件:
src/backend/optimizer/path/costsize.c、src/backend/optimizer/path/clausesel.c、src/backend/command/analyze.c
第 11 章:查询规划器 — Join 顺序与路径生成
- Path 生成:
make_one_rel()→ 基表访问路径 → Join 路径 - 访问路径类型:SeqScan、IndexScan、IndexOnlyScan、BitmapScan
- Join 方式:NestLoop、HashJoin、MergeJoin 的创建条件与代价比较
- Join 顺序搜索:动态规划 (standard_join_search) vs GEQO 遗传算法
- 并行路径生成
(
create_plain_partial_paths) - 实验:
EXPLAIN (ANALYZE, BUFFERS)输出与 planner 决策的对照 - 源码焦点文件:
src/backend/optimizer/path/allpaths.c、src/backend/optimizer/path/joinpath.c、src/backend/optimizer/geqo/geqo_main.c
第 12 章:执行器与表达式求值
- Executor 的火山模型:
ExecInitNode()→ExecProcNode()→ExecEndNode() - 算子实现示例:
SeqNext()、ExecNestLoop()、ExecHashJoin() - 表达式求值:EEO (Expression Evaluation) —
ExecInterpExpr() ExprState与ExprEvalStep:表达式从 tree 编译成 opcode 序列- Tuple 的 slot 机制:
TupleTableSlot(virtual / heap / minimal) - 排查:查询 hang
住不动时的诊断路径——
pg_stat_activity的wait_event_type+wait_event字段精读(LWLock/Lock/IO/BufferPin各表示什么)、pg_blocking_pids()追踪锁等待链、EXPLAIN (ANALYZE, BUFFERS, SETTINGS)输出的”计划行数 vs 实际行数”差异定位到哪个算子出了问题 - 源码焦点文件:
src/backend/executor/execMain.c、src/backend/executor/nodeHashjoin.c、src/backend/executor/execExprInterp.c
第 13 章:JIT 编译
- PG 的 JIT 基础设施:LLVM 集成
(
llvmjit.c) - 哪些操作会被 JIT 编译:表达式求值与 tuple 变形
- JIT 的触发阈值 (
jit_above_cost) 和决策流程 - 实测:OLAP 查询的 JIT 加速效果与边界
- 源码焦点文件:
src/backend/jit/llvm/llvmjit.c、src/backend/jit/llvm/llvmjit_expr.c
第四部分:索引内部机制(第 14-17 章)
第 14 章:B-Tree 索引
- PG B-Tree 的页面布局:
BTPageOpaque、high key、rightlink - 插入路径:
_bt_doinsert()→ 找页 → 空间检查 → 插入 → 页面分裂 - 页面分裂 (
_bt_split()) 的完整流程:选择分裂点 → 分配新页 → 迁移元组 → 插入父指针 - PG 12+ 的 B-Tree 去重
(
deduplicate_items):何时触发,如何压缩 - B-Tree 的 WAL 记录类型与恢复
- 实验:
bt_page_items()观察 B-Tree 页面内部 - 源码焦点文件:
src/backend/access/nbtree/nbtinsert.c、src/backend/access/nbtree/nbtsplitloc.c、src/backend/access/nbtree/nbtdedup.c
第 15 章:GiST 索引
- GiST
的抽象接口:
Consistent、Union、Penalty、PickSplit、Compress/Decompress - 搜索算法:
gistgettuple()的深度优先 + Consistent 过滤 - 插入与页面分裂:Penalty → PickSplit
- 几何类型 (
point_ops) 的 GiST 实现 - 全文搜索 (
tsvector_ops) 的 GiST 实现 - 源码焦点文件:
src/backend/access/gist/gist.c、src/backend/access/gist/gistget.c
第 16 章:GIN 索引
- GIN 的倒排结构:entry tree + posting tree / posting list
- Fast Update:pending list 的设计与后台合并
- 搜索流程:
gingetbitmap()的 bitmap AND/OR - 全文搜索 (
tsvector) 与数组 (_int4) 的 GIN 实现 - GIN vs GiST 的选择:写性能、读性能、存储开销三角权衡
- 源码焦点文件:
src/backend/access/gin/gininsert.c、src/backend/access/gin/ginget.c
第 17 章:BRIN 与其他索引
- BRIN 的设计哲学:范围摘要替代逐行索引
- BRIN 的内部结构:revmap + summary tuples
- BRIN 适用场景量化:IO 敏感型大数据表
- Hash 索引的 WAL 记录现状与使用边界
- Bloom 索引 (
bloom扩展) 的使用场景 - 源码焦点文件:
src/backend/access/brin/brin.c、src/backend/access/hash/hash.c
第五部分:扩展性与高可用(第 18-20 章)
第 18 章:流复制
- WAL Sender 的启动流程:
WalSndLoop()→XLogSendPhysical()→ 网络 - WAL Receiver:
WalRcvLoop()→ 写 WAL → 恢复 - 同步复制 (synchronous_commit = on / remote_write / remote_apply) 的语义与等待机制
- Failover 与 Timeline 协商:
history文件的格式与作用 - Primary-standby
冲突:
max_standby_streaming_delay的 trade-off - Slot 机制:
pg_replication_slots的内部结构 - 事件/坑:Replication slot
溢出的多米诺效应——standby 宕机 → slot 阻止 WAL 回收 →
pg_wal目录填满磁盘 → primary crash。wal_keep_sizevs slot 的互相影响。Promote 后的 split-brain 风险和pg_rewind的机制边界 - 排查:
pg_stat_replication的write_lag/flush_lag/replay_lag三层延迟指标的精确语义和零值陷阱(=0 可能是真的没延迟,也可能是 slot 卡住了没推进);standby 查询被 cancel 的conflict_reason解读 - 源码焦点文件:
src/backend/replication/walsender.c、src/backend/replication/walreceiver.c
第 19 章:逻辑复制与逻辑解码
- 逻辑解码 (
LogicalDecodingContext) 的内部流程 - Reorder Buffer:事务重排与
snapshot重建 - Output Plugin (
pgoutput) 的协议 - Publication / Subscription 模型的内核实现
- 逻辑复制的冲突检测与处理
- 逻辑复制 vs 流复制:各自的适用场景与不可行场景
- 坑/排查:逻辑复制的冲突类型全解——
update_missing/delete_missing/duplicate_key的根因和修复边界;subscription 被 disable 后的数据追平策略(pg_subscription的subenabled状态机);序列不是逻辑复制的范围——自增主键冲突的经典陷阱。大事务对逻辑复制的延迟放大效应(reorder buffer 必须等事务 COMMIT 才释放) - 源码焦点文件:
src/backend/replication/logical/decode.c、src/backend/replication/logical/reorderbuffer.c、src/backend/replication/pgoutput/pgoutput.c
第 20 章:扩展系统与 FDW
- Extension 的 hook
机制全景:
planner_hook、ExecutorStart_hook、ProcessUtility_hook等 - FDW (Foreign Data Wrapper)
的接口:
FdwRoutine中的核心回调 postgres_fdw的 pushdown 机制:哪些操作能推到远端执行?- 扩展的 SQL 接口 (
CREATE EXTENSION) 与.control文件 - 扩展对 planner 的影响:
GetForeignRelSize()→GetForeignPaths()→GetForeignPlan() - 源码焦点文件:
src/include/foreign/fdwapi.h、src/backend/foreign/foreign.c、contrib/postgres_fdw/
第六部分:运维实战与故障排查(第 21-26 章)
这 6 章是内核机制向生产现场的反向应用。每章以 2-5 个真实故障模式驱动,用”现象→根因(内核机制解释)→排查路径→修复边界”的结构组织。不写 runbook,写”为什么这样做、不这样做的后果”。
第 21 章:监控体系与告警设计
- 从内核机制出发定义”应该监控什么”——不是照搬 Grafana dashboard 模板
- 核心监控维度:
- 连接与进程:
pg_stat_activity的连接数/状态/wait_event分布、idle in transaction 持续时间 - 存储与膨胀:
pg_stat_user_tables的n_dead_tup、n_tup_del/n_tup_upd比率、age(relfrozenxid)趋势 - WAL 与复制:
pg_stat_replication的write_lag/flush_lag/replay_lag、pg_stat_wal的写入速率、pg_ls_waldir()的磁盘占用趋势 - 查询性能:
pg_stat_statements的mean_exec_time/stddev_exec_time突变检测、calls频率异常 - 锁与等待:
pg_locks的granted=false计数、pg_stat_activity的wait_event_type=Lock占比 - 系统资源:
shared_buffers命中率(但要讲清楚”命中率 99% 也可能有问题”)、temp_files的生成速率和大小
- 连接与进程:
- 告警阈值的内核依据:
autovacuum_freeze_max_age的 2 亿为什么不是安全线、n_dead_tup / n_live_tup到什么比例必须关注 - 坑:
pg_stat_statements的queryid冲突(不同查询的 hash 碰撞)、track_io_timing=on的性能开销量化、pg_stat_activity在高并发时查询本身的代价 - 工具集成:Prometheus
pg_exporter的指标选型、pgBadger日志分析的补充价值、pgCenter的实时诊断
第 22 章:经典故障模式与排查手册
- 故障 1:连接风暴 —
max_connections的 100 不是”最多 100 个用户同时在线”;每个连接 fork 一个 backend 进程(~5-10MB 私有内存),100 个连接就是 500MB-1GB;真正的风险是work_mem——每个连接可能同时持有多个work_mem配额(hash join + sort + materialize),200 连接 × 默认 4MB work_mem × 3 操作 = 2.4GB。排查:pg_stat_activity看state=active的连接、pg_prepared_xacts看 2PC 泄露 - 故障 2:事务 ID wraparound 危机 — 从
autovacuum_freeze_max_age到ForceTransactionId的完整危机链;pg_stat_activity中看到autovacuum: VACUUM pg_catalog.pg_authid (to prevent wraparound)意味着什么;数据库被强制只读后唯一的恢复路径 - 故障 3:replication slot 溢出 — standby
宕机 → slot
restart_lsn不推进 → WAL 段无法回收 →pg_wal占满磁盘 → primary PANIC。为什么wal_keep_size和 slot 会叠加而不是取最大值?排查:pg_replication_slots的active=f且restart_lsn距当前很久 → 确认 standby 不可恢复 → 删除 slot 的后果和替代方案 - 故障 4:OOM 连锁 kill — Linux OOM
killer 杀掉 PG backend 进程的后果:Postmaster
看到子进程异常退出 →
restart_after_crash=on→ 触发 full restart → 所有连接断开。为什么这不是”杀掉一个连接重连就行”?排查:dmesg看 OOM kill 日志 →pg_stat_activity配合EXPLAIN (ANALYZE, BUFFERS)定位内存大户 →work_mem/hash_mem_multiplier调优 - 故障 5:长事务(idle in
transaction)的隐性破坏 — 持锁不释放阻塞 DDL →
VACUUM 无法回收该事务开始之后的 dead tuple →
表膨胀加速。排查:
pg_stat_activity的state=idle in transaction+xact_start时间 →pg_locks看它持有哪些锁 →pg_terminate_backend()的副作用 - 每个故障配 Mermaid 时序图,标注事件节点和排查断点
第 23 章:性能异常调查方法论
- 调查链(不是工具箱罗列,而是按顺序的解释-排查-确认链路):
pg_stat_statements:定位高耗时/高频率 queryid → 用mean_exec_time+stddev_exec_time区分”一直慢”和”偶尔慢”EXPLAIN (ANALYZE, BUFFERS, SETTINGS):看计划行数 vs 实际行数的差异定位估算错误 → 看Buffers: shared hit/read定位 I/O 瓶颈pg_stat_activity+wait_event:如果查询在”等”——是等锁(Lock)、等 I/O(IO/DataFileRead)、等 LWLock(LWLock等待哪个模块)、还是等客户端(ClientRead,这不是 PG 的问题)pg_locks+pg_blocking_pids():追踪锁等待链——谁在锁我、我在锁谁- OS 层:
iostat/iotop确认磁盘、perf/bpftrace追踪内核调用栈
- 特殊场景:
- “有时快有时慢”:计划缓存 (
plan_cache_mode) 与 generic plan vs custom plan 的切换逻辑、auto_explain+log_min_duration_statement捕获慢查询及其 plan - “CPU 100% 但没慢查询”:排查 LWLock 自旋(spinlock contention)、extended stats 构建的 CPU 消耗、表达式索引的维护开销
- “IO 打满但 shared_buffers 命中率 99%”:命中率骗局——大量 shared_buffers 用在重复读同一小批页面,大表 seqscan 每次都在读磁盘
- “有时快有时慢”:计划缓存 (
- 工具链集成:
pg_stat_statements+pg_wait_sampling+pg_stat_kcache的组合使用
第 24 章:数据恢复与损坏应对
- PITR(Point-in-Time
Recovery)的内部机制与操作边界:
- 基于 WAL archive 的 PITR
恢复流程——
restore_command的调用时机、recovery_target_time/recovery_target_xid的精度和陷阱 - PITR 的三个关键操作窗口:最后一次 base backup、WAL
archive 的完整性、
pg_wal中尚未 archive 的 WAL 段——缺少任何一个都无法完整恢复 - PITR 恢复后
timeline的 fork 原理和影响——为什么恢复后的 primary 不能再回切到原 primary?pg_rewind的--source-pgdata机制
- 基于 WAL archive 的 PITR
恢复流程——
- 页面级损坏的诊断与应对:
pg_checksums工具的原理和校验粒度——它检查什么、不检查什么zero_damaged_pages=on的语义——它不是”修复损坏的页面”,而是”遇到损坏页面时跳过它返回零填充”,数据已经丢了- WAL 损坏的应对:
pg_resetwal的内部操作——它清空 WAL 但不碰数据文件,代价是”所有设置了 hint bit 的 tuple 可见性不确定”→ 必须VACUUM FULL重建
- pg_dump / pg_restore 的策略边界:
pg_dump -Fc(custom format)vs-Fd(directory)的并行恢复差异pg_restore --jobs=N的内部调度——为什么不是 N 个 worker 同时跑,而是每个 object 分配到一个 worker- 大表 pg_dump 的
--table分表 dump 策略、COPYvsINSERT的恢复性能差异、--disable-triggers+--no-owner的恢复加速
第 25 章:大版本升级与迁移实战
- pg_upgrade 的内部机制与”为什么 –link
这么快”:
--link模式:不是复制数据文件,而是创建 hard link 指向相同的 inode——0 数据拷贝。但代价:一旦升级后发现问题,pg_upgrade没有”回滚”机制——原集群的硬链接已经被新集群共享,修改一处影响两边--clone模式(支持的文件系统):CoW 快照,类似 hard link 但允许独立修改- pg_upgrade 执行流程:
pg_upgrade --check做预检(检查 catalog schema 兼容性、扩展版本、数据类型二进制兼容性)→pg_upgrade实际执行(copy pg_catalog → link 用户表 → 更新 pg_control)→analyze_new_cluster.sh - 前置条件:所有扩展在目标版本必须存在且版本兼容、
pg_upgrade_support函数必须可用、磁盘空间至少需要复制pg_catalog的空间
- 逻辑复制跨版本迁移:
- 用逻辑复制做低停机时间大版本升级的流程:新版本 standby → 逻辑订阅所有表 → 等待追平 → 切应用流量
- 优势和陷阱:可回滚(切换 DNS 即可)、但序列和 large object 不在逻辑复制范围内、DDL 复制需要 PG 17+ 或手动同步
- 常见坑:
- pg_upgrade 后
pg_stat_statements/auto_explain等扩展的 shared_preload_libraries 配置丢失 pg_dump+pg_restore迁移后统计信息消失——ANALYZE必须重跑pg_upgrade后vacuumdb --analyze-only不能省——否则优化器用假统计跑出灾难计划locale/encoding/lc_collate不兼容导致 pg_upgrade 失败——pg_upgrade --check最先检查这个
- pg_upgrade 后
- 迁移方案决策树:小库(<10GB)→
pg_dump/restore,中库 → pg_upgrade
--link,大库(>500GB)+ 不允许长时间停机 → 逻辑复制迁移
第 26 章:配置陷阱与生产最佳实践
- 每一条配一个”为什么这个默认值在你的场景下可能是错的”和”配错之后的症状及排查”:
shared_buffers:不是越大越好——超过 ~8-10GB 后 double buffering(shared_buffers + OS page cache 同时缓存)导致额外内存开销,且 checkpoint 写脏页的量变大;正确值依赖pg_buffercache的实际观察work_mem:最危险的 GUC——它不是”每个查询用 4MB”,而是”查询中每个 sort/hash/bitmap/materialize 操作各用 4MB”。一个 parallel hash join × 3 workers × 100 并发 = 灭顶之灾。hash_mem_multiplier(PG 15+) 的联动效应effective_cache_size:它不分配内存——只影响优化器的索引扫描 vs 顺序扫描代价估算。设太小,优化器不信任 OS cache 会缓存索引页,过度倾向 seqscanrandom_page_cost:在 NVMe SSD 上默认 4.0 严重高估随机 I/O 成本,导致优化器偏向 seqscan。正确值需要pg_test_fsync实测后换算fsync=off:PG 中最危险的配置——关闭后COMMIT只是把 WAL 写到了 OS buffer,还没刷盘。crash 时正在提交的事务可以在 WAL 恢复时不完整。唯一合法的使用场景:批量数据导入后用 pg_dump 重建(因为源数据还在)synchronous_commit=off:事务提交不等 WAL 刷盘 → 最多丢wal_writer_delay(默认 200ms)的已提交事务。适合”丢了也能重做”的 session,不适合金融交易huge_pages=onvshuge_pages=try:Docker/K8s 环境下透明大页被禁用的静默退化——shared_buffers配了 8GB 但实际用了 4KB 页,TLB miss 显著增加。诊断:/proc/meminfo的HugePages_Total、PG 启动日志中的 huge page 分配信息maintenance_work_mem:只在VACUUM、CREATE INDEX、ALTER TABLE等维护操作中使用——设太小导致VACUUM需要多趟 scan index、CREATE INDEX的 external merge 阶段极慢idle_in_transaction_session_timeout:为什么必须设——防止一个忘了 commit 的 psql session 锁住整个表的 DDL。设为 0(默认)的风险log_lock_waits+deadlock_timeout的组合——deadlock_timeout不只是死锁检测间隔,也影响log_lock_waits的报告阈值log_min_duration_statement+auto_explain的组合陷阱——设太低导致日志洪水、auto_explain.log_analyze=on会实际执行 query 导致 overhead 叠加
- 每条配查验方法——不是”设成 X 就好了”,是”通过什么视图和日志确认当前设置有问题”
四、篇目依赖关系
强依赖图
第 1 章 (进程/内存) ──→ 第 2 章 (页面/元组) ──→ 第 3 章 (MVCC/CLOG) ──→ 第 4 章 (WAL)
│ │ │ │
└────────┬───────────────┘ │ │
↓ ↓ ↓
第 5 章 (Buffer Manager) ←──────────────── 共享依赖 ──→ 第 6 章 (Lock Manager)
│ │
↓ ↓
第 7 章 (事务/子事务) ──→ 第 8 章 (VACUUM) │
│ │ │
└───────┬───────────────┘ │
↓ │
第 9 章 (解析/重写) │
↓ │
第 10 章 (统计/代价) ←── 第 11 章 (Join/路径) │
│ │ │
└───────────┬───────────┘ │
↓ │
第 12 章 (执行器) ──→ 第 13 章 (JIT) │
│ │
┌───────┬───────┼───────┬───────┐ │
↓ ↓ ↓ ↓ ↓ │
第 14 章 第 15 章 第 16 章 第 17 章 ──→ 第 18 章 (流复制) │
(B-Tree) (GiST) (GIN) (BRIN) │ │
↓ │
第 19 章 (逻辑复制) │
│ │
↓ │
第 20 章 (扩展/FDW) │
│ │
↓ │
┌─────────┴──────────┐ │
↓ ↓ │
第 21 章 (监控) 第 22 章 (故障) │
│ │ │
└────┬───────────────┘ │
↓ │
第 23 章 (性能调查) │
│ │
┌─────────┬──┴──┬──────────┐ │
↓ ↓ ↓ ↓ │
第 24 章 第 25 章 第 26 章 │
(恢复) (升级) (配置陷阱) │
│
第 3 章已有的 mvcc 文章作为前置阅读 ←─────────────────────────┘
弱依赖(可独立阅读)
- 第 15、16、17 章(GiST / GIN / BRIN)只依赖第 2 章(页面布局)和基本索引概念
- 第 13 章(JIT)只需第 12 章执行器基础
- 第 20 章(扩展/FDW)可与第四部分并行读
- 第 21、22 章(监控、故障)依赖第 1、3、8 章的基础概念,但可独立作为运维入口
- 第 23 章(性能调查)需要第 6、10、12 章的内核知识,但可先读方法论再回头补细节
- 第 24-26 章(恢复、升级、配置)强烈依赖前置机制章节——不建议在读完对应机制章之前仅按第 22/23 章排查,但可作为”遇到事故时按图索骥”的速查入口
已有文章的关系
db/mvcc/mvcc.md可作为第 3 章的前置阅读,第 3 章在前者基础上深入 CLOG、hint bit、快照可扩展性storage/27-wal-aries(WAL/ARIES 通用原理) 可作为第 4 章的前置阅读storage/26-buffer-pool(Buffer Pool 通用原理) 可作为第 5 章的前置背景storage/25-btree-engineering(B-Tree 通用原理) 可作为第 14 章的前置背景
五、阅读路径
路径 A:日常 PG 用户 → 想理解内核
1 → 2 → 3 → 5 → 10 → 11 → 14
先理解进程/内存/页面这些”物理基础”,再看查询规划和索引,这是日常工作最相关的内核知识。
路径 B:数据库内核开发者
1 → 2 → 5 → 4 → 6 → 7 → 12 → 14
先看存储层(页面、Buffer、WAL)和并发控制(锁、事务),再看执行器和索引——这是写一个数据库引擎的核心路径。
路径 C:SRE / DBRE → 排查问题
1 → 3 → 8 → 4 → 18 → 19 → 22 → 23 → 26 从
MVCC/VACUUM 的运维关键点入手(膨胀、wraparound),再看 WAL
和复制——是高可用和性能故障的主战场。第 22 章(经典故障)和第
23 章(性能调查)是排查速查手册,第 26
章(配置陷阱)是配置审计清单。
路径 D:应用开发者 → 优化查询
9 → 10 → 11 → 12 → 13 → 14 → 23 → 26
从解析/重写/规划/执行/JIT 的完整 SQL
生命周期,理解为什么某个查询慢。第 23 章提供性能调查方法,第
26 章避免配置踩坑。
路径 E:准备给 PG 贡献代码
1 → 2 → 5 → 6 → 14 → 20
从进程/页面/Buffer/Lock
这些你必须懂的基础设施,到索引和扩展系统——PG
内核贡献的入门路径。
路径 F:运维老兵 → 快速查 PG 特有的坑
22 → 26 → 8 → 18 → 23
从故障模式和配置陷阱切入(你不是来学内核的,你是来了解”PG
有什么不同于 MySQL/Oracle/其他数据库的特别坑”),第 8 章搞懂
VACUUM(PG 最独特的运维点),第 18 章搞懂复制,第 23 章学会
PG 的调查工具链。
路径 G:接手一个正在出问题的 PG 集群
22(定位故障模式)→ 23(性能调查方法)→ 21(建立监控防线)→ 26(审计配置)→ 24(如果需要恢复数据)
先止血、再调查、再建立防护、再审计配置、最后处理数据恢复——按紧急程度排序。
六、来源台账
A 级来源(支撑关键结论)
源码
- PostgreSQL 主仓库
(
https://git.postgresql.org/git/postgresql.git),版本范围 PG 16–17- 每章标注焦点文件路径与关键函数
- 涉及前代版本差异时,引用特定 tag 或 commit
官方文档
- PostgreSQL Official Documentation, Chapter 50–55 (Internals)
- PostgreSQL Official Documentation, Chapter 28–30 (Reliability & WAL)
- PostgreSQL Official Documentation, Chapter 51 (Index Access Methods)
- PostgreSQL Official Documentation, Chapter 61 (B-Tree Internals)
- PostgreSQL Official Documentation, Chapter 64 (GiST Internals)
- PostgreSQL Official Documentation, Chapter 65 (GIN Internals)
论文
- Hellerstein, J. M. & Stonebraker, M. Readings in Database Systems, 5th Edition (Red Book). MIT Press, 2015. — 第 1 章 (数据模型与查询语言) 与第 4 章 (存储与索引) 直接相关
- Stonebraker, M. & Rowe, L. A. The Design of POSTGRES. SIGMOD 1986. — 历史起点,理解设计意图
- Stonebraker, M. The Design of the POSTGRES Storage System. VLDB 1987.
- Chamberlin, D. D. et al. A History of the Evaluation of System R. CACM 1981. — 查询优化器的学术背景
- Selinger, P. G. et al. Access Path Selection in a Relational Database Management System. SIGMOD 1979. — PG 代价模型的学术起源
- Mohan, C. et al. ARIES: A Transaction Recovery Method Supporting Fine-Granularity Locking and Partial Rollbacks Using Write-Ahead Logging. TODS 1992. — WAL 的理论基础
- Berenson, H. et al. A Critique of ANSI SQL Isolation Levels. SIGMOD 1995. — 快照隔离的形式化定义
- Cahill, M. J. et al. Serializable Isolation for Snapshot Databases. SIGMOD 2008. — SSI 的理论基础(PG 9.1+ 的 Serializable 实现)
- Graefe, G. The Cascades Framework for Query Optimization. IEEE Data Eng. Bull. 1995. — 查询优化器架构参考
开发者讨论
- pgsql-hackers 邮件列表归档:关键设计决策的讨论(如 snapshot scalability patch、B-Tree deduplication、VACUUM parallelization)
- PGCon / PGConf.dev 演讲:核心开发者的设计思路阐述
B 级来源(辅助参考)
- PostgreSQL Wiki:内核开发指南、待办事项、性能数据
- 核心开发者博客:Robert Haas、Tom Lane、Andres Freund、Heikki Linnakangas、Peter Geoghegan
- PostgreSQL Internals 电子书 (Egor Rogov) — 对比现有讲解与 PG 17 差异
- The Internals of PostgreSQL (Hironobu Suzuki) — 在线文档,版本稍旧但结构清晰
- use-the-index-luke.com (Markus Winand) — 索引行为解释
C 级来源(线索,不直接引用)
- Planet PostgreSQL 聚合博客
- Stack Overflow / DBA Stack Exchange 上的高票回答
- 中文论坛的 PG 内核讨论
七、实验台账
基础实验环境
| 项目 | 说明 |
|---|---|
| PG 版本 | 优先 PG 17 (latest stable),涉及版本差异时补充 PG 16 |
| 编译 | --enable-debug --enable-cassert --enable-depend |
| 工具 | gdb、pageinspect、pg_buffercache、pgstattuple、pg_stat_statements、auto_explain |
| 测试数据集 | pgbench 默认数据 + 定制倾斜分布数据 |
各章实验计划
| 章节 | 实验内容 | 工具/方法 |
|---|---|---|
| 第 2 章 | 观察 heap page 内部字节 | pageinspect 扩展 |
| 第 3 章 | 追踪 CLOG 页面分配与可见性判断 | gdb 断点 + pageinspect |
| 第 4 章 | 测量 WAL 写入带宽与 wal_sync_method
影响 |
pg_stat_wal + pg_waldump |
| 第 5 章 | 观察 shared_buffers 使用与 clock sweep | pg_buffercache +
EXPLAIN (BUFFERS) |
| 第 6 章 | 构造死锁与锁等待链 | pg_locks +
pg_blocking_pids() |
| 第 8 章 | 量化 VACUUM 前后的 dead tuple 数量与膨胀 | pgstattuple +
pg_stat_user_tables |
| 第 10 章 | 构造统计信息不准导致糟糕计划的场景 | 定制倾斜数据 + EXPLAIN ANALYZE |
| 第 11 章 | 追踪 Join 顺序搜索与路径选择 | debug_print_plan + gdb |
| 第 14 章 | 观察 B-Tree 页面分裂与去重 | bt_page_items() +
bt_metap() |
| 第 18 章 | 搭建 primary-standby、测量复制延迟 | pg_stat_replication |
| 第 19 章 | 搭建逻辑复制、构造冲突场景 | Publication/Subscription DDL |
| 第 21 章 | 部署 Prometheus pg_exporter、配置告警规则 | pg_exporter + Grafana |
| 第 22 章 | 复现连接风暴/OOM/wraparound/slot 溢出/长事务故障 | 定制负载脚本 + gdb 断点 |
| 第 23 章 | 构造统计不准/锁等待/IO 瓶颈三类慢查询并排查定位 | pg_stat_statements + EXPLAIN +
wait_event |
| 第 24 章 | 执行 PITR 恢复、模拟 WAL 损坏与 pg_resetwal | pg_basebackup + pg_waldump |
| 第 25 章 | 执行 pg_upgrade –link 升级 16→17、构造升级失败场景 | 两个 PG 安装 + 测试数据 |
| 第 26 章 | 对比不同配置下的性能差异、构造 work_mem 过量/fsync off/huge_pages 退化 | pgbench + pg_buffercache +
/proc/meminfo |
Benchmark 要求
涉及性能结论时,至少交代: - 硬件:CPU
型号/核数、内存容量、磁盘类型 (NVMe/SSD/HDD) -
OS:内核版本、文件系统、挂载选项 - PG
版本与关键配置:shared_buffers、work_mem、max_wal_size、wal_level
- 负载:pgbench scale
factor、客户端数、运行时长、预热轮次 - 每次至少跑 3
轮,使用中位数
八、与其他系列的联动
| 本站已有系列 | 联动方式 |
|---|---|
storage/ (79) |
WAL/ARIES、Buffer Pool、B-Tree 的通用原理在 PG 中的具体实现 |
db/mvcc/mvcc.md (1) |
第 3 章直接以该文为前置,扩展 CLOG/hint bit/snapshot scalability |
db/lsm-tree/ (9) |
B-Tree vs LSM 的工程取舍在 PG 索引章节中对照 |
distributed/ (70) |
一致性与复制的理论背景,在 PG 流复制/逻辑复制章节引用 |
os/ (111) |
fork()、shmem、fsync、fdatasync、信号量——PG
大量使用这些 OS 原语 |
linux/ (14) |
内核层面的 I/O、内存、进程调度对 PG 性能的影响 |
九、边界承诺
承诺
- 核心概念配源码路径和关键函数名,读者能自己追踪
- 关键流程配 Mermaid 时序图或泳道图
- 每章提供实验方法(扩展、gdb 断点、SQL 脚本),可复现
- 运维章(第 21-26 章)的故障模式给出可复现的构造方法和排查断点
- 解释”为什么这样设计”而不是只列举 API
- 跨 PG 版本标注差异(至少覆盖 PG 16 和 17)
- 性能结论来自真实执行
- 配置建议配查验方法——不是”设成 X 就好了”,是”通过什么视图/日志确认当前设置有问题”
不承诺
- 不替代 PG 官方文档——只讲”为什么”和”怎么做决策”,不枚举所有 GUC 参数
- 不覆盖存储引擎之外的模块(如 PL/pgSQL 虚拟机、JSON/JSONB 处理细节)
- 不覆盖 cloud managed 服务(RDS、Aurora、Cloud SQL 等——db-frontier 系列涉及)
- 不覆盖 PG 生态中的所有扩展(PostGIS、Citus、TimescaleDB 等选型问题)
- 不预测 PG 18 或未来版本的设计决策
- 不提供”万能调优公式”——调优必须基于实际的负载和硬件
- 不覆盖操作系统运维(内核参数调优、磁盘阵列配置、网络拓扑设计等——这些在 OS/存储系列中已有覆盖)
- 不覆盖非 PG 的灾难恢复方案(如异地多活、跨云容灾等——分布式系列已有覆盖)
十、写作顺序
第一批(基础层,无外部依赖)
- 第 1 章:进程模型与共享内存
- 第 2 章:页面布局与元组格式
第二批(存储与并发,依赖第一批)
- 第 3 章:MVCC 实现(扩展已有文章)
- 第 5 章:Buffer Manager
- 第 4 章:WAL 内部机制
- 第 6 章:锁管理器
第三批(事务与运维,依赖第二批)
- 第 7 章:事务与子事务
- 第 8 章:VACUUM 与 Freezing
第四批(查询层,依赖第一/二批)
- 第 9 章:查询解析与重写
- 第 10 章:统计信息与代价模型
- 第 11 章:Join 顺序与路径生成
- 第 12 章:执行器与表达式求值
- 第 13 章:JIT 编译
第五批(索引与高可用,依赖基础层 + 部分查询层)
- 第 14 章:B-Tree 索引
- 第 15 章:GiST 索引
- 第 16 章:GIN 索引
- 第 17 章:BRIN 与其他索引
第六批(复制与扩展,依赖 WAL + 事务)
- 第 18 章:流复制
- 第 19 章:逻辑复制与逻辑解码
- 第 20 章:扩展系统与 FDW
第七批(运维实战,依赖全部前六批的基础机制知识)
- 第 21 章:监控体系与告警设计
- 第 22 章:经典故障模式与排查手册
- 第 23 章:性能异常调查方法论
第八批(运维实战续 + 配置,强烈依赖前七批)
- 第 24 章:数据恢复与损坏应对
- 第 25 章:大版本升级与迁移实战
- 第 26 章:配置陷阱与生产最佳实践
十一、待确认问题
- PG 版本锚定:以 PG 17 为主吗?PG 18 预计 2026 年 10 月发布,是否等到 PG 18 再开始?
- 系列规模:20 章扩展到 26 章是否合适?运维部分(第 21-26 章)是否考虑拆成独立系列?
- 题目风格:现有文章标题多为陈述式(“数据库 MVCC:快照隔离到底隔离了什么”),本系列是否沿用?
- 运维章的深度 vs 宽度:第 22 章(故障)现在是 5 种典型故障,是否扩展到更多?第 26 章(配置)现在是 11 个 GUC,是否包含更多?
- 发布时间节奏:26 章预计需要较长时间——是否先发前 20 章内核部分,第 21-26 章运维部分单独发?
- MySQL/InnoDB 对照:是否在某些章节(MVCC、锁、VACUUM vs undo)做 PG vs InnoDB 对比?运维部分是否加入 MySQL DBA 转向 PG 的对照提示?
- 示例代码语言:源码分析用 C,实验和图示生成用 Python?运维章的监控 SQL 和排查脚本用什么语言?
- 故障复现实验:第 22 章的 5 种故障模式需要真实复现(构造连接风暴、触发 OOM、导致 slot 溢出等)——这些实验有破坏性,是否在独立 VM 中进行?
附录:关键 GUC 参数速查(写作用)
以下 GUC 是 PG 内核行为的关键控制点,写作涉及对应章节时必须理解其内核含义,不能只写调优规则。
| GUC | 相关章节 | 内核含义 |
|---|---|---|
shared_buffers |
第 5 章 | Buffer pool 大小,直接影响 clock sweep 频率 |
wal_level |
第 4、18、19 章 | 控制 WAL 记录类型集合,决定复制能力 |
wal_sync_method |
第 4 章 | WAL 写入的刷盘策略 |
max_wal_size /
min_wal_size |
第 4、8 章 | Checkpoint 触发与 WAL 回收 |
checkpoint_timeout |
第 4 章 | Checkpoint 的时间触发 |
autovacuum_* |
第 8 章 | Autovacuum 的触发与限速 |
vacuum_freeze_min_age |
第 8 章 | Freezing 的触发条件 |
default_statistics_target |
第 10 章 | ANALYZE 采样精度 |
random_page_cost |
第 10 章 | 代价模型的核心常量 |
jit_above_cost |
第 13 章 | JIT 编译的触发阈值 |
max_worker_processes |
第 1、20 章 | 后台进程上限 |
synchronous_commit |
第 18 章 | 同步复制的持久性语义 |
max_standby_streaming_delay |
第 18 章 | 备机查询取消的时间窗口 |
max_replication_slots |
第 18、19 章 | 复制 slot 上限 |
deadlock_timeout |
第 6 章 | 死锁检测间隔 |
log_lock_waits |
第 6、26 章 | 锁等待超时时写入日志——配合 deadlock_timeout 使用 |
checkpoint_completion_target |
第 4、22 章 | Checkpoint 的 IO 摊平策略,直接影响写入高峰 |
autovacuum_vacuum_cost_delay |
第 8、22 章 | Autovacuum 的 IO 限速——设太高可能追不上 dead tuple 产生速度 |
autovacuum_analyze_scale_factor |
第 10、21 章 | 触发 auto-analyze 的表变化比例——insert-only 表可能永远达不到默认 0.1 |
hash_mem_multiplier |
第 23、26 章 | PG 15+ 的 hash join 内存倍数——与 work_mem 联动计算 hash 操作可用内存 |
maintenance_work_mem |
第 8、25、26 章 | VACUUM / CREATE INDEX / ALTER TABLE 等维护操作的内存上限 |
effective_cache_size |
第 10、26 章 | 优化器对 OS page cache + shared_buffers 的估算——不分配内存,只影响代价计算 |
huge_pages |
第 26 章 | 大页内存开关——Docker/K8s 中静默退化为小页的排查 |
idle_in_transaction_session_timeout |
第 22、26 章 | 防止忘记 commit 的 session 持锁不释放 |
log_min_duration_statement |
第 21、23、26 章 | 慢查询日志阈值——配合 auto_explain 使用,注意日志洪水 |
track_io_timing |
第 21、23 章 | 启用 IO 计时统计——对查询性能有微小影响 |
plan_cache_mode |
第 23 章 | 计划缓存策略——generic plan vs custom plan 的强制选择 |
fsync |
第 4、24、26 章 | 关掉 COMMIT 不刷盘——最危险的配置之一 |
restart_after_crash |
第 22 章 | OOM kill 后 PG 是否自动重启——默认 on,关了就没自动恢复 |
zero_damaged_pages |
第 24 章 | 遇到校验和不匹配的页面时跳过并返回零填充——数据已丢,不是修复 |
规划版本:v2,2026-06-16 主要变更:v2 — 扩展系列从 20→26 章,新增第六部分「运维实战与故障排查」(监控/故障/调查/恢复/升级/配置陷阱),增强现有章节(4/8/10/12/18/19)的运维/排查维度,新增阅读路径 F/G(运维老兵/应急排查),新增 8 个 GUC 参数速查项 下一步:确认本文档中的待确认问题 → 编写系列 index.md → 开始第一批写作(第 1、2 章)
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【PG 内核】PostgreSQL 内核机制深度拆解
从进程模型到磁盘页面、从 MVCC 到流复制——对 PostgreSQL 内核做完整的源码级拆解。不止步于源码分析:26 篇中 6 篇是运维实战——经典故障的根因与排查路径、性能调查的五层工具链、配置陷阱与恢复边界。面向想读懂 PG 内核源码、在生产环境排查过问题、准备给 PG 贡献代码的工程师。