跳转到内容

PostgreSQL 关系型数据库

20 道题
分类
数据库
题目数
20 道
已阅读 0 / 20 题
1 PostgreSQL 的进程架构与内存结构

答案:

PostgreSQL 采用 多进程(Multi-Process)架构,每个客户端连接对应一个独立的 Backend Process,进程间通过共享内存通信。

进程模型:

graph TD
    subgraph PM["Postgres Process Hierarchy"]
        PMain["Postmaster
(主守护进程)"] PMain --> SysLogger["SysLogger
日志采集"] PMain --> BgWriter["BgWriter
后台写进程"] PMain --> WalWriter["WalWriter
WAL 写入"] PMain --> AutoVacuum["AutoVacuum
Worker"] PMain --> StatsCollector["StatsCollector
统计信息"] PMain --> Checkpointer["Checkpointer
检查点"] PMain --> Archiver["Archiver
WAL 归档"] PMain --> Backend1["Backend Process
Client Connection 1"] PMain --> Backend2["Backend Process
Client Connection 2"] PMain --> BackendN["Backend Process
Client Connection N"] end

共享内存结构(Shared Memory):

区域职责配置参数
Shared Buffers数据页缓存,减少磁盘 I/Oshared_buffers(物理内存 25%)
WAL BufferWAL 日志写入缓冲区wal_buffers(默认 16MB)
CLOG(Commit Log)事务提交状态位图固定大小,不可配置
Lock Space锁管理器状态(行锁/表锁)max_locks_per_transaction
Proc Array活跃进程信息数组max_connections 决定大小

各辅助进程职责:

进程职责触发机制
Postmaster监听连接、Fork Backend Process、崩溃后重启守护进程常驻
Checkpointer执行 Checkpoint,将脏页刷入磁盘checkpoint_timeoutmax_wal_size
BgWriter持续将共享缓冲区中较少使用的脏页写入磁盘周期性(默认 200ms)
WalWriter将 WAL Buffer 刷新到 WAL Segment事务提交或 WAL Buffer 满
AutoVacuum Launcher调度 AutoVacuum Workerautovacuum_naptime
Stats Collector收集查询与表访问统计信息周期性(默认 500ms)
Archiver将已写完的 WAL Segment 复制到归档位置archive_command 配置
SysLogger将 Postgres 日志重定向到文件logging_collector = on

客户端连接处理流程:

1. Postmaster 监听 TCP 5432 端口
2. 客户端发起连接请求
3. Postmaster Fork 一个 Backend Process
4. Backend Process 执行客户端 SQL,直接返回结果
5. 连接关闭 → Backend Process 退出
2 PostgreSQL 的 Shared Buffers 与 OS Page Cache 协同机制

答案:

PostgreSQL 的 I/O 路径存在双层缓存:Shared Buffers(数据库内部缓存)和 OS Page Cache(操作系统文件缓存),二者协同但不重叠。

读写 I/O 路径:

graph TD
    subgraph User["用户空间"]
        SB["Shared Buffers
(PostgreSQL 管理)"] WAL_BUF["WAL Buffer"] end subgraph Kernel["内核空间"] PG_CACHE["OS Page Cache
(内核管理)"] FS["File System"] end subgraph HW["硬件"] DISK["磁盘 / SSD"] end SQL["SQL 查询"] --> SB SB -->|"脏页刷入"| PG_CACHE PG_CACHE -->|"writeback"| DISK DISK -->|"预读 / 直接 I/O"| PG_CACHE PG_CACHE -->|"缺页读取"| SB WAL_BUF -->|"WAL 写入"| PG_CACHE SB -->|"WAL 写入绕过 Shared Buffers"| WAL_BUF

Key Insight:

  • PostgreSQL 的 shared_buffers 使用 直接 I/O?不,PostgreSQL 使用 buffered I/O —— 数据先写入 OS Page Cache,再由内核刷入磁盘。但 WAL 写入通过 wal_sync_method 控制刷盘策略(open_datasync / fdatasync / fsync_writethrough)。
  • effective_cache_size 用于查询规划器估算 OS Page Cache 中可用的缓存大小,非实际分配。该值不影响内存使用,仅影响查询规划器的索引选择。

Dirty Page 刷新机制对比:

组件刷新策略参数控制
BgWriter持续扫描 Shared Buffers,将使用频率较低的脏页刷入 OS Cachebgwriter_delay(默认 200ms)
Checkpointer周期性或 WAL 满时检查点,强制所有脏页刷入磁盘checkpoint_timeout(默认 5min)
WalWriterWAL Buffer 达到阈值或事务提交时刷盘wal_writer_delay(默认 200ms)

双层缓存常见问题:

问题表现解决方案
shared_buffers 过大OS Page Cache 不足,导致频繁系统调用控制在物理内存的 25%,不超过 8GB
双缓存浪费PostgreSQL 写入 2 次(1 次 Shared Buffers + 1 次 OS Page Cache)使用 wal_sync_methoddirectio(仅 WAL)
脏页积压BgWriter 跟不上写入速度 → Checkpointer 写入风暴调大 bgwriter_lru_maxpages
3 PostgreSQL 的 MVCC 实现原理

答案:

PostgreSQL 的 MVCC(Multi-Version Concurrency Control)通过为每个元组保留多个版本来实现读不阻塞写、写不阻塞读的并发控制机制。实现方式为 行级 Snapshot + 可见性规则,非 InnoDB 的 Undo Log 模式。

元组结构(Heap Tuple Header):

graph LR
    subgraph Tuple["Heap Tuple"]
        TH["Tuple Header (23 bytes)"]
        Bitmap["Null Bitmap"]
        UData["User Data"]
    end
    
    subgraph TH["Tuple Header"]
        XMIN["xmin
创建事务 ID"] XMAX["xmax
删除 / 锁定事务 ID"] CID["cid
命令 ID(CommandId)"] TVF["t_infomask
标志位"] OID["oid
元组 OID(可选)"] end

关键字段:

字段大小说明
xmin4 bytes创建该版本的事务 ID
xmax4 bytes删除 / 锁定的事务 ID(0 表示未删除)
cid4 bytes创建命令的 Command Counter
t_ctid6 bytes指向当前/新版本的物理位置 (page, offset)
t_infomask2 bytes元组状态标志(已提交/已回滚/冻结等)

可见性判断规则(Snapshot):

Tuple 对于当前事务可见的条件:

1. xmin 已提交 ∧ (xmax == 0 ∨ xmax 未提交 ∨ xmax == 当前事务)
   → 可见(正常数据)

2. xmin 未提交 ∧ xmin == 当前事务
   → 可见(自己插入的未提交数据)

3. xmin 已提交 ∧ xmax 已提交 ∧ xmax ≠ 当前事务
   → 不可见(已被其他事务删除)

4. xmin 为冻结事务(Frozen XID)
   → 始终可见(用于 VACUUM FREEZE 后的表)

HOT(Heap-Only Tuple)更新优化:

当更新的列不影响索引且空闲空间充足时,PostgreSQL 使用 HOT 更新,新版本在同一 Page 内以 t_ctid 链式连接,无需更新索引:

graph LR
    subgraph Page["Data Page"]
        T1["Old Tuple
t_ctid → (0,2)"] T2["New Tuple
t_ctid → (0,2) 自身"] HP["Page Header
-> pd_special"] end T1 -.->|"单页内指针"| T2

死元组(Dead Tuple)的产生场景:

操作产生死元组说明
UPDATE1 个死元组旧版本标记为死亡
DELETE1 个死元组被删除版本标记为死亡
INSERT新插入的为活元组
SELECT … FOR UPDATE取决于实现行锁不产生新元组版本
4 PostgreSQL 的事务隔离级别与 Snapshot 机制

答案:

PostgreSQL 支持 SQL 标准定义的四种事务隔离级别,但其实现核心基于 Snapshot,由 xmin/xmax 和事务状态决定每个事务能看到的元组版本。

隔离级别实现矩阵:

隔离级别脏读不可重复读幻读实现机制
Read Uncommitted不允许(等同 RC)可能可能SSI 实现,实际等价 Read Committed
Read Committed不允许可能可能每条 SQL 获取新 Snapshot
Repeatable Read不允许不允许允许(PG 实现防幻读)整个事务使用同一 Snapshot
Serializable不允许不允许不允许Serializable Snapshot Isolation (SSI)

Read Committed Snapshot 行为:

每条 SQL 语句开始前获取一个新的 Snapshot:

Transaction A:
  BEGIN;
  SELECT * FROM t WHERE id = 1;  -- 获取 Snapshot S1
  -- ... 等待 ...
  SELECT * FROM t WHERE id = 1;  -- 获取 Snapshot S2(与 S1 可能不同)
  COMMIT;

Transaction B:
  UPDATE t SET val = 'b' WHERE id = 1;  -- 在 A 的两次查询之间
  COMMIT;

A 的第一次查询看到旧值,第二次查询看到新值(不可重复读)。

Repeatable Read Snapshot 行为:

整个事务使用第一个 Snapshot:

Transaction A:
  BEGIN;
  SELECT * FROM t WHERE id = 1;  -- 获取 Snapshot S1,整个事务使用此 Snapshot
  -- ... 等待 ...
  SELECT * FROM t WHERE id = 1;  -- 使用同一 Snapshot S1
  COMMIT;

A 的两次查询结果相同。但 A 尝试 UPDATE 或 DELETE 其他事务已修改的行时会收到 Serialization Failure 错误(可序列化冲突检测)。

Serializable 的 SSI 实现:

PostgreSQL 的 Serializable 隔离级别使用 Serializable Snapshot Isolation (SSI),在 RR 基础上检测 Read-Write Conflicts(读写冲突)Write-Skew(写偏斜)

graph TD
    SI["Snapshot Isolation
(T1, T2 各自 Snapshot)"] SI --> RW["Read-Write Conflict
T1 读 → T2 写"] SI --> WW["Write-Write Conflict
T1 写 → T2 写"] RW --> DSSI["Dangerous Structure
(SI 中 RW 反向依赖)"] DSSI --> C1["Commit T1"] DSSI --> C2["Commit T2"] C1 --> SSI_Abort["SSI 检测 → Abort T2
(Serialization Failure)"] C2 --> SSI_Abort

SSI 各隔离级别的 Snapshot 获取时机:

隔离级别xmin 行为冲突检测
Read Committed每个 Statement 更新 xmin 范围不加 SSI predicate locks
Repeatable Read事务级固定 xmin无 predicate locks
Serializable事务级固定 xmin加上 predicate locks 检测序列化冲突
5 PostgreSQL 的 VACUUM 与 Autovacuum 机制

答案:

VACUUM 负责回收死元组(Dead Tuple)占用空间、更新可见性映射(VM)中的标记、冻结事务 ID(Freeze),防止事务 ID 回卷(XID Wraparound)导致数据库不可用。

VACUUM 类型对比:

类型是否回收空间给 OS是否阻塞读写是否更新 FSM/VM是否冻结行适用场景
VACUUM(常规)否(留给本表复用)否(不影响并发读写)常规维护,定期执行
VACUUM FULL是(收缩表文件大小)是(独占锁 AccessExclusiveLock)空间释放紧急场景(业务低峰执行)
VACUUM FREEZE强制冻结防事务 ID 回卷的维护操作
Autovacuum否(仅短暂阻塞 DDL)按需(autovacuum_freeze_max_age自动调度,无需人工干预

VACUUM 执行流程:

graph TD
    START["VACUUM 开始"]
    START --> Scan["扫描表(从头至尾扫 Page)"]
    Scan --> VM_Check["检查 VM(Visible Map)
已全可见 Page 跳过"] VM_Check --> ScanContent["扫描 Page 内容"] ScanContent --> DT["收集 Dead Tuple TID"] DT --> Remove["清除索引条目
(每个索引单独扫描)"] Remove --> Compact["整理 Page 内空间
(移动活元组)"] Compact --> UpdateFSM["更新 FSM
(Free Space Map)"] UpdateFSM --> UpdateVM["更新 VM
(新标记可用 Page)"] UpdateVM --> Freeze["冻结 XID
(超过 freeze_min_age)"] Freeze --> UpdateStats["更新 pg_class 统计"] UpdateStats --> END["VACUUM 完成"]

Autovacuum 触发条件:

Autovacuum 的触发基于死元组数量与表大小的比例。每个 Worker 处理一个表,满足以下任一公式即触发:

触发公式:
  pg_stat_user_tables.n_dead_tup > autovacuum_vacuum_threshold
  + autovacuum_vacuum_scale_factor * pg_class.reltuples

简化:
  死元组 > 50 + 0.1 * 表总行数

关键参数链:

参数默认值作用
autovacuum_max_workers3同时运行的 Worker 数(每 Worker 一表)
autovacuum_naptime1min检查需要处理的表的间隔
autovacuum_vacuum_threshold50最小死元组触发数
autovacuum_vacuum_scale_factor0.1表大小的 10% 死元组触发
autovacuum_vacuum_cost_delay2ms每次 I/O 成本后的暂停时间
autovacuum_vacuum_cost_limit200每次暂停前的成本上限
autovacuum_freeze_max_age200 million最大未冻结事务数,接近触发防回卷 VACUUM

事务 ID 回卷原理:

Transaction IDs 是一个 32 位环状计数器:
0 → 1 → 2 → ... → 2³²-1 → 0(开始新的环)

问题:
  XID = 3 的事务看到的 XID = 2³²-3 实际上是未来的事务(因环状回卷)
  → 需要冻结(Freeze)将 XID 标记为 FrozenXID(=2),使其对任何事务都可见
  
防回卷策略:
  1. 普通 VACUUM:冻结 age > autovacuum_freeze_min_age 的行
  2. 防回卷 VACUUM:当表达到 autovacuum_freeze_max_age(默认 2 亿)
     时会强制触发 VACUUM(即使 autovacuum 关闭)
  3. 接近回卷时(1.5 亿 ~ 2 亿),PostgreSQL 发出 WARNING:
    "database X may be shut down to avoid wraparound data loss"
6 PostgreSQL 的 B-Tree 索引与扩展索引类型

答案:

PostgreSQL 的默认索引类型是 B-Tree,此外提供 GiST、GIN、BRIN、Hash 等索引类型,覆盖从精确查询到全文搜索、空间查询到压缩索引的多样化场景。

B-Tree 索引结构:

graph TD
    subgraph Root["Root Page
(metapage)"] RP["(5,2)
(10,8)
(15,3)"] end subgraph Internal["Internal Pages"] IP1["(1,5)
(3,2)"] IP2["(6,10)
(8,4)"] IP3["(11,15)
(13,7)"] end subgraph Leaf["Leaf Pages
(双向链表)"] LP1["(1, tid1)
(2, tid2)
(3, tid3)"] LP2["(5, tid4)
(6, tid5)
(8, tid6)"] LP3["(10, tid7)
(11, tid8)
(13, tid9)"] end RP --> IP1 RP --> IP2 RP --> IP3 IP1 --> LP1 IP1 --> LP2 IP2 --> LP2 IP2 --> LP3 IP3 --> LP3 LP1 <-->|"← →"| LP2 LP2 <-->|"← →"| LP3

B-Tree 关键特性:

特性说明
页大小默认 8KB(BLCKSZ),可通过 --with-blocksize 编译时指定
填充因子fillfactor = 90(默认 90%),预留 10% 空间给 HOT 更新
分裂Page 满时分裂为两个 50% 填充的 Page
删除Dead Index Tuple 由 VACUUM 回收,空间可复用
重复键重复键在 Leaf Page 中连续存放,无溢出页

索引类型对比:

类型算子支持适用场景大小特性
B-Tree<, <=, =, >=, >, BETWEEN, IN, IS NULL, LIKE 'abc%'精确匹配 + 范围查询标准大小
GIN(Generalized Inverted Index)@>, <@, ?, `?,?&`全文搜索、JSONB、数组、tsvector
GiST(Generalized Search Tree)`«, &<, &>, », --, ~=, @, ~, &&`空间数据、全文搜索(rank)、范围类型
BRIN(Block Range Index)<, <=, =, >=, >(同 B-Tree)时序数据、日志表(天然有序)极小(千分之一大小)
Hash=等值查询较小,不支持排序
SP-GiST`«, &<, », &>, --, ~=, @, ~, &&`空间分区、网络地址

各类索引适用场景详解:

-- B-Tree:默认索引,精确匹配和范围查询
CREATE INDEX idx_btree_time ON events (created_at);
SELECT * FROM events WHERE created_at >= '2025-01-01' AND created_at < '2025-02-01';

-- BRIN:时序数据大表索引(比 B-Tree 小 100~1000 倍)
CREATE INDEX idx_brin_time ON events USING BRIN (created_at)
  WITH (pages_per_range = 32);
SELECT * FROM events WHERE created_at BETWEEN '2025-01-01' AND '2025-01-02';

-- GIN:全文搜索
CREATE INDEX idx_gin_content ON documents USING GIN (to_tsvector('english', content));

-- GiST:空间查询
CREATE INDEX idx_gist_location ON locations USING GiST (coord);
SELECT * FROM locations WHERE coord <@ '((0,0),(10,10))'::box;

-- Hash:等值查询(B-Tree 也可满足,Hash 更小)
CREATE INDEX idx_hash_email ON users USING HASH (email);

索引选择决策树:

查询类型:
  ├── 精确匹配(=)+ 排序 → B-Tree
  ├── 范围查询(BETWEEN, >, <)→ B-Tree 或 BRIN(时序数据)
  ├── 全文搜索/JSONB/数组 → GIN
  ├── 空间/地理数据 → GiST 或 SP-GiST
  └── 等值唯一查询(不长扫描)→ Hash
7 PostgreSQL 的查询执行流程与优化器

答案:

PostgreSQL 的查询执行流程从 SQL 文本到结果集需经过 Parser → Analyzer → Rewriter → Planner/Optimizer → Executor 五个阶段。

查询执行流程:

graph TD
    SQL["SQL Query"]
    SQL --> Parser["Parser
语法分析 → Parse Tree"] Parser --> Analyzer["Analyzer
语义分析 → Query Tree"] Analyzer --> Rewriter["Rewriter
规则重写 → Rewritten Query Tree"] Rewriter --> Planner["Planner/Optimizer
→ Plan Tree"] Planner --> Executor["Executor
执行 Plan → Tuple"] Executor --> Result["Result Set"] subgraph Optimizer["Optimizer 内部"] P1["预处理
谓词下推 / 常量折叠"] P2["生成候选路径
扫表方式 + 连接方式"] P3["评估代价
基于 pg_class + pg_stats"] P4["选择最优 Plan"] P1 --> P2 --> P3 --> P4 end Planner --> Optimizer

扫描方式(Scan Methods):

方式说明适用场景
Seq Scan顺序扫描全表大表全量查询、无过滤条件
Index ScanB-Tree 索引定位 + 回表选择性高(< 5%)的过滤
Index Only Scan直接在索引列上返回,不回表查询列全部在索引中
Bitmap ScanBitmap Index Scan + Bitmap Heap Scan选择性中等(5%~20%)
Tid Scan按元组 ID(ctid)直接读取WHERE ctid = '(0,1)'
Subquery Scan子查询包装子查询结果作为输入
Function ScanFROM 子句中的函数SELECT * FROM generate_series(1,10)

连接方式(Join Methods):

方式算法复杂度适用场景
Nested Loop外层逐行匹配内层索引O(N × log M)小表驱动大表 + 内表有索引
Hash Join构建 Hash 表后探测O(N + M)大表等值连接,无索引
Merge Join排序后归并O(N log N + M log M)预排序数据,非等值连接

优化器代价模型参数:

参数默认值说明
seq_page_cost1.0顺序页读取代价(基准单位)
random_page_cost4.0随机页读取代价(SSD 设为 1.1)
cpu_tuple_cost0.01处理单行代价
cpu_index_tuple_cost0.005处理索引行代价
cpu_operator_cost0.0025执行单次运算符代价
effective_cache_size4GBOS Page Cache 大小估计(影响索引选择)
8 PostgreSQL 的执行计划解读与 EXPLAIN 使用

答案:

PostgreSQL 使用 EXPLAIN / EXPLAIN ANALYZE / EXPLAIN (BUFFERS, ANALYZE) 获取查询执行计划。

EXPLAIN 输出字段含义:

                                    QUERY PLAN
---------------------------------------------------------------------------
 Gather  (cost=1000.00..15000.00 rows=5000 width=32)
   Workers Planned: 2
   ->  Parallel Seq Scan on orders  (cost=0.00..14000.00 rows=2083 width=32)
         Filter: (status = 'pending')
字段说明
cost=1000.00..15000.00启动代价(1000)到总代价(15000)
rows=5000优化器估算的输出行数
width=32输出行的平均宽度(字节)
Workers Planned: 2并行 Worker 数量

聚合统计字段:

Execution Time: 12.345 ms          -- 总执行时间(ANALYZE 额外开销不计入)
Planning Time: 0.234 ms            -- 规划时间
Buffers: shared hit=102 read=5     -- 缓存命中 102 页,磁盘读取 5 页

EXPLAIN 各选项对比:

选项额外信息是否执行 SQL适用场景
EXPLAIN估算的 Plan Tree快速查看计划结构
EXPLAIN ANALYZE真实执行时间、行数、Loop 次数验证估算准确性
EXPLAIN (BUFFERS, ANALYZE)缓存命中率、磁盘读取数I/O 性能诊断
EXPLAIN (ANALYZE, TIMING OFF)关闭每次计时(减少 ANALYZE 开销)减少影响生产性能
EXPLAIN (FORMAT JSON)JSON 格式输出编程解析

常见索引使用问题诊断:

-- 检查预估 vs 实际行数差异
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE created_at > '2025-01-01';
-- 关键观察:rows=10000(预估)vs actual rows=500000(实际)
-- → 统计信息过旧,执行 ANALYZE

-- 检查是否有 Parallel Seq Scan 替代索引扫描
-- 如果随机页读取成本设置过高,优化器倾向 Seq Scan
SET random_page_cost = 1.1;  -- SSD 环境下

执行计划异常诊断清单:

现象可能原因解决方案
预估行数与实际行数差异大统计信息滞后ANALYZE table_name
大量 Seq Scan 但在应该用索引时random_page_cost 过高(HDD 默认)SSD 改为 1.1
Nested Loop 全表扫描内表连接列缺失索引添加索引
排序操作 O(N²)work_mem 不足导致磁盘排序增大 work_mem
多种候选 Plan 不稳定参数嗅探使用 plan_cache_mode = force_generic_plan
9 PostgreSQL 的 WAL(Write-Ahead Log)机制

答案:

WAL(Write-Ahead Log)是 PostgreSQL 保证数据一致性和持久性的核心机制:在数据页写入磁盘之前,必须先确保对该数据页的修改日志已被写入持久化存储。

WAL 写入原则:

WAL Before Data(先写日志,再写数据):

1. 事务修改数据页(Shared Buffers 中)
2. 生成 REDO 日志记录 → WAL Buffer
3. 事务提交 → WalWriter 将 WAL Buffer 刷入 WAL Segment(触发 fsync)
4. Checkpointer 在 Checkpoint 后将脏数据页刷入磁盘

恢复原则:
  数据库崩溃后,从最近 Checkpoint 开始重放所有 WAL
  → 保证已提交事务不丢失,未提交事务回滚

WAL Segment 结构:

组件大小说明
WAL Segment16MB(默认)每个 WAL 文件 16MB
WAL Page8KBSegment 内页大小,与 BLCKSZ 一致
WAL Record可变单条日志记录(如:一个 Page 修改)
WAL Record Header24 bytes记录类型、长度、检查点等信息
XLOG Page Header20 bytesPage 级头部,含 LSN 引用
WAL Buffer16MB(默认)内存中缓存未刷入 Segment 的 WAL Record

LSN(Log Sequence Number)的作用:

LSN 是 WAL 中的字节偏移量,用于唯一标识日志位置:

LSN = WAL Segment 偏移 + 该 Segment 内字节偏移

示例:0/16B3748 → Segment 偏移 0x16B0000, 页内偏移 0x3748

LSN 写入数据页头 pd_lsn 字段,用于:

  1. 恢复起点:Checkpoint 记录 WAL 重放起点 LSN
  2. 页级恢复:数据页头部的 pd_lsn 确定哪些 WAL Record 需要重放到此页
  3. 复制进度pg_stat_replication 中的 sent_lsn / write_lsn / flush_lsn / replay_lsn

Checkpoint 机制:

graph LR
    subgraph Timeline["Checkpoint 时间线"]
        CP1["Checkpoint
T=0min"] CP2["Checkpoint
T=5min"] Dirty1["Dirty Page
刷入"] CP1 --> Dirty1 Dirty1 --> T1["(写入数据文件)"] T1 --> CP2 WAL["WAL 持续写入"] WAL --> CP1 WAL --> CP2 end

Checkpoint 关键参数:

参数默认值作用
checkpoint_timeout5min两次定时 Checkpoint 的最大间隔
max_wal_size1GBCheckpoint 之间 WAL 最大增长量
min_wal_size80MB保留的最小 WAL 空间
checkpoint_completion_target0.5Checkpoint 完成时间占 checkpoint_timeout 的比例
checkpoint_warning30sCheckpoint 频率超过此值记录警告

WAL 配置参数:

参数说明生产建议
wal_levelWAL 记录级别replica(默认)或 logical
wal_compressionWAL 页压缩(PG15+ 优化)on(兼容 WAL 归档和复制)
wal_buffersWAL 缓冲区大小shared_buffers 的 1/32,建议 64MB~256MB
wal_sync_methodWAL 刷盘方法fdatasync(推荐)或 open_sync
wal_writer_delayWalWriter 刷盘频率100ms~200ms
10 PostgreSQL 的 Streaming Replication 架构

答案:

PostgreSQL 的 Streaming Replication 基于 WAL 的实时传输,Primary 以流式方式将 WAL 数据发送给 Standby,Standby 持续接收并重放 WAL。

复制架构:

graph LR
    subgraph Primary["Primary Server"]
        PG_P["PostgreSQL Primary"]
        WAL_P["WAL
写入"] WALSender["WAL Sender
(walsender process)"] PG_P --> WAL_P --> WALSender end subgraph Standby["Standby Server"] WALReceiver["WAL Receiver
(walreceiver process)"] WALWrite["WAL
写入"] Startup["Startup Process
(WAL Apply)"] PG_S["PostgreSQL Standby
(read-only)"] WALReceiver --> WALWrite --> Startup --> PG_S end WALSender -->|"TCP 流
WAL 数据"| WALReceiver subgraph Config["复制参数"] P1["primary_conninfo
连接 Primary 信息"] P2["primary_slot_name
复制槽"] P3["hot_standby = on
允许只读查询"] P4["synchronous_standby_names
同步模式"] end

复制角色与文件布局:

Primary:
  $PGDATA/pg_wal/          -- WAL 段文件
  $PGDATA/pg_hba.conf      -- 允许复制连接
  $PGDATA/postgresql.conf  -- wal_level = replica 等参数

Standby:
  $PGDATA/standby.signal   -- Standby 模式标记文件(PG12+)
  $PGDATA/pg_wal/          -- 接收的 WAL 段
  $PGDATA/postgresql.conf  -- hot_standby = on

流复制配置示例(Primary):

# postgresql.conf
wal_level = replica
max_wal_senders = 10
wal_keep_size = 1024          # MB, 保留的 WAL 量(未使用复制槽时的兜底)
max_replication_slots = 10
hot_standby = on
# pg_hba.conf 添加复制用户
host replication replicator <standby_ip>/32 scram-sha-256

Standby 配置示例(PG12+):

# 创建基础备份
pg_basebackup -h <primary_ip> -D $PGDATA -U replicator -P --wal-method=stream

# standby.signal 文件
touch $PGDATA/standby.signal
# postgresql.conf
primary_conninfo = 'host=<primary_ip> port=5432 user=replicator password=xxx'
primary_slot_name = 'standby_1'
hot_standby = on
11 PostgreSQL 的同步复制与异步复制

答案:

PostgreSQL 支持同步复制和异步复制两种模式。异步模式下 Primary 不等待 Standby 确认;同步模式下 Primary 等待至少一个 Standby 确认 WAL 写入后才返回事务提交成功。

同步复制确认层级(synchronous_commit):

参数值Primary 行为数据安全性提交延迟
off不等待 WAL 写入本机 WAL,直接返回最低(OS crash 丢失)最低
local等待本地 WAL 写入本地磁盘标准(实例级持久化)
remote_write等待本地 WAL 写入 + Standby 写入 OS Cache中(Standby OS crash 可能丢)
on(默认)等待本地 WAL 写入 + Standby 写入磁盘高(Standby 实例级持久化)较高
remote_apply等待本地 WAL 写入 + Standby 已应用最高(Standby 可见该事务)最高

synchronous_standby_names 配置:

# 至少 ANY 1 个 Standby 同步确认
synchronous_standby_names = 'ANY 1 (*)'

# 精确指定同步 Standby(前 n 个)
synchronous_standby_names = 'FIRST 2 (standby1, standby2, standby3)'

# 不配置 = 异步复制(Standby 仅异步追赶)

同步复制对性能的影响:

事务提交延迟 = MAX(本地 WAL fsync, 远端 Standby WAL fsync RTT)

场景:
  本地 fsync:           ~2ms
  跨 AZ (同区域):       RTT ~1-3ms → 总延迟 ~3-5ms
  跨区域 (同大陆):      RTT ~10-30ms → 总延迟 ~12-32ms
  跨洲:                 RTT ~150-300ms → 总延迟 ~152-302ms (不推荐)

同步复制配置权衡:

同步级别数据可靠性性能损失推荐场景
异步最多丢 ~1 个 WAL Segment (16MB)读多写少、分析型负载
同步(1 Standby, 同 AZ)1 个 Standby 确认+~1ms一般 OLTP
同步(1 Standby, 跨 AZ)AZ 级容灾+~3ms生产 OLTP,建议
同步(2 Standby, 跨 2 AZ)跨 AZ 容灾+~5ms金融级
12 PostgreSQL 的物理备份与 PITR 恢复

答案:

PostgreSQL 的物理备份与 PITR(Point-In-Time Recovery)基于 WAL 归档实现。基础备份 + 连续 WAL 归档 + 备份时间点可以恢复到任意时间点。

备份与恢复架构:

graph TD
    subgraph Backup["备份体系"]
        BaseBackup["pg_basebackup
全量基础备份"] WALArchive["连续 WAL 归档
archive_command"] end subgraph Storage["备份存储"] S3["S3 / GCS / Azure Blob"] NFS["NFS / 本地挂载"] end subgraph Recovery["恢复"] RestoreCmd["restore_command
从归档获取 WAL"] PITR["recovery_target_time / XID"] Promote["恢复完成后提升"] end BaseBackup --> S3 WALArchive --> S3 S3 --> RestoreCmd NFS --> BaseBackup NFS --> WALArchive RestoreCmd --> PITR PITR --> Promote

pg_basebackup 备份命令:

# 创建全量基础备份(实时流复制,不停服)
pg_basebackup -h <primary> -D /backup/base_20250801 \
  -U replicator -P --wal-method=stream \
  --format=tar --gzip --compress-level=9

# 增量备份(自备为基础之上,需 pgBACKrest 或 WAL-G)

WAL 归档配置:

# postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'
archive_timeout = 60          # 强制归档间隔(秒)

PITR 恢复步骤:

# postgresql.conf (恢复实例)
restore_command = 'cp /archive/%f %p'

# recovery.conf (PG11-) 或 recovery.signal (PG12+)
recovery_target_time = '2025-08-01 14:30:00+08'
recovery_target_xid = '12345678'     # 按事务 ID 恢复
recovery_target_lsn = '0/16B3748'    # 按 LSN 恢复
recovery_target_inclusive = true     # 是否包含目标点

全量备份方案对比:

工具备份类型增量支持并行压缩加密恢复速度
pg_basebackup物理全量tar+gz中(全量恢复)
pgBackRest物理全量/增量/差异是(多线程)zst/bz2/gz是(gpg)快(增量恢复)
WAL-G物理全量/增量lz4/zstd/brotli是(AES)快(增量恢复)
Barman物理全量/增量gz/bz2是(gpg)

验证备份可恢复性:

# pgBackRest 验证
pgbackrest --stanza=production check

# 从备份恢复到临时实例
pg_ctl -D /tmp/pg_restore start
# 验证数据完整性
pg_checksums -D /tmp/pg_restore
13 PostgreSQL 的表空间与分区表

答案:

PostgreSQL 的表空间(Tablespace)将数据库对象映射到文件系统路径。分区表(Partitioning)将大表拆分为多个物理子表。

表空间(Tablespace)管理:

-- 创建表空间
CREATE TABLESPACE fast_space LOCATION '/ssd/pg_tablespace';
CREATE TABLESPACE archive_space LOCATION '/hdd/archive_tablespace';

-- 在指定表空间创建对象
CREATE TABLE orders (...) TABLESPACE fast_space;
CREATE INDEX idx_orders_time ON orders (created_at) TABLESPACE fast_space;

-- 移动表空间
ALTER TABLE orders SET TABLESPACE archive_space;

-- 查看表空间大小
SELECT
  spcname,
  pg_size_pretty(pg_tablespace_size(spcname))
FROM pg_tablespace;

分区表(Table Partitioning):

PG10+ 支持声明式分区,PG11+ 支持 Hash 分区和 DEFAULT 分区,PG12+ 支持 FOR VALUES IN 列表分区,PG13+ 支持分区行触发 BEFORE/AFTER。

-- 范围分区(时序数据典型方案)
CREATE TABLE orders (
    id BIGSERIAL,
    created_at TIMESTAMPTZ NOT NULL,
    amount NUMERIC(10,2),
    customer_id BIGINT
) PARTITION BY RANGE (created_at);

-- 创建分区子表(每月一个分区)
CREATE TABLE orders_202501 PARTITION OF orders
    FOR VALUES FROM ('2025-01-01') TO ('2025-02-01')
    TABLESPACE fast_space;

CREATE TABLE orders_202502 PARTITION OF orders
    FOR VALUES FROM ('2025-02-01') TO ('2025-03-01')
    TABLESPACE fast_space;

-- 列表分区
CREATE TABLE customers (
    id BIGSERIAL,
    region TEXT,
    name TEXT
) PARTITION BY LIST (region);

CREATE TABLE customers_apac PARTITION OF customers
    FOR VALUES IN ('CN', 'JP', 'KR');
CREATE TABLE customers_emea PARTITION OF customers
    FOR VALUES IN ('DE', 'FR', 'UK');

-- Hash 分区(均匀分布)
CREATE TABLE sessions PARTITION BY HASH (user_id);
CREATE TABLE sessions_p0 PARTITION OF sessions FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE sessions_p1 PARTITION OF sessions FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE sessions_p2 PARTITION OF sessions FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE sessions_p3 PARTITION OF sessions FOR VALUES WITH (MODULUS 4, REMAINDER 3);

分区维护操作:

-- 添加新分区
CREATE TABLE orders_202503 PARTITION OF orders
    FOR VALUES FROM ('2025-03-01') TO ('2025-04-01');

-- 分离分区(转换为独立表)
ALTER TABLE orders DETACH PARTITION orders_202501;

-- 删除分区
DROP TABLE orders_202501;

-- 手动附加分区
ALTER TABLE orders ATTACH PARTITION orders_202503
    FOR VALUES FROM ('2025-03-01') TO ('2025-04-01');

分区裁剪(Partition Pruning):

PostgreSQL 的查询规划器在匹配分区键过滤条件时自动裁剪无关分区:

-- 查询仅扫描 orders_202501 和 orders_202502
EXPLAIN SELECT * FROM orders
WHERE created_at BETWEEN '2025-01-15' AND '2025-02-15';

分区对比:

维度声明式分区(Declarative)继承式分区(Inheritance)
支持版本PG10+PG8+(旧方式)
分区创建PARTITION OFCREATE TABLE xxx INHERITS (parent)
约束排除自动依赖 CHECK 约束
INSERT 路由自动路由到正确分区(PG12+)需要触发器
UPDATE 跨分区PG11+ 支持不支持
索引管理支持分区索引需在子表手动创建
推荐度强烈推荐不推荐
14 PostgreSQL 的锁机制与死锁检测

答案:

PostgreSQL 的锁体系分为表级锁行级锁和**咨询锁(Advisory Lock)**三层。死锁检测通过 Deadlock Timeout 定期扫描等待图实现。

表级锁模式(从弱到强):

锁模式SQL 操作冲突的锁模式
ACCESS SHARESELECTACCESS EXCLUSIVE
ROW SHARESELECT FOR UPDATE/FOR SHAREEXCLUSIVE, ACCESS EXCLUSIVE
ROW EXCLUSIVEINSERT, UPDATE, DELETESHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE, ACCESS EXCLUSIVE
SHARE UPDATE EXCLUSIVEVACUUM, ANALYZE, CREATE INDEX CONCURRENTLYSHARE UPDATE EXCLUSIVE, SHARE, EXCLUSIVE, ACCESS EXCLUSIVE
SHARECREATE INDEX (非并发)ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE ROW EXCLUSIVE, EXCLUSIVE, ACCESS EXCLUSIVE
SHARE ROW EXCLUSIVECREATE TRIGGER几乎所有写锁
EXCLUSIVEREFRESH MATERIALIZED VIEW CONCURRENTLY几乎所有锁
ACCESS EXCLUSIVEDROP TABLE, TRUNCATE, VACUUM FULL所有锁(排他)

行级锁:

锁类型获取方式冲突
FOR UPDATESELECT … FOR UPDATE其他 FOR UPDATE / FOR NO KEY UPDATE / FOR SHARE / FOR KEY SHARE
FOR NO KEY UPDATEUPDATE/DELETE(默认)其他 FOR UPDATE / FOR SHARE / FOR KEY SHARE
FOR SHARESELECT … FOR SHARE其他 FOR UPDATE / FOR NO KEY UPDATE / FOR KEY SHARE
FOR KEY SHARE外键检查其他 FOR UPDATE / FOR SHARE

死锁检测机制:

graph TD
    T1["Transaction 1
Holds: Row R1 Lock
Waits: Row R2 Lock"] T2["Transaction 2
Holds: Row R2 Lock
Waits: Row R1 Lock"] T1 -->|"等待"| R2["Row R2 Lock"] T2 -->|"等待"| R1["Row R1 Lock"] subgraph Deadlock["死锁条件"] Cycle["T1 → R2 → T2 → R1 → T1
形成循环等待"] end Deadlock -> DDetect["Deadlock Timeout
(默认 1s) 触发检测"] DDetect -> Abort["Abort T1(代价较小者)"]

死锁检测参数:

参数默认值说明
deadlock_timeout1s死锁检测扫描间隔(设置越小检测越快,但 CPU 开销越大)
log_lock_waitsoff记录超过 deadlock_timeout 的锁等待日志
max_locks_per_transaction64每事务可持有的最大锁数

锁监控查询:

-- 查看当前所有等待锁的关系
SELECT
  blocked_locks.pid AS blocked_pid,
  blocked_activity.query AS blocked_query,
  blocking_locks.pid AS blocking_pid,
  blocking_activity.query AS blocking_query,
  blocked_activity.wait_event_type || ': ' || blocked_activity.wait_event AS wait_event
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_locks.pid = blocked_activity.pid
JOIN pg_catalog.pg_locks blocking_locks
  ON blocking_locks.locktype = blocked_locks.locktype
  AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
  AND blocking_locks.relation IS NOT DISTINCT FROM blocked_blocks.relation
  AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
  AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
  AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
  AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
  AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
  AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
  AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
  AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_locks.pid = blocking_activity.pid
WHERE NOT blocked_locks.granted;
15 PostgreSQL 的 FDW(Foreign Data Wrapper)

答案:

FDW(Foreign Data Wrapper)是 PostgreSQL 的 SQL/MED(SQL Management of External Data)实现,允许 PostgreSQL 查询外部数据源(其他 PG 实例、MySQL、CSV 文件、Kafka、MongoDB 等)。

FDW 架构:

graph TD
    subgraph Local["本地 PostgreSQL"]
        FDW["Foreign Data Wrapper
(extension)"] FST["Foreign Server
(连接配置)"] FM["Foreign Table
(外部表映射)"] end subgraph Remote["远程数据源"] D1["PostgreSQL
mysql_fdw"] D2["MySQL
mysql_fdw"] D3["S3/CSV
file_fdw"] D4["MongoDB
mongo_fdw"] end SQL["SELECT * FROM remote_table"] --> FM FM --> FST FST --> FDW FDW --> D1 FDW --> D2 FDW --> D3 FDW --> D4

常用 FDW 扩展:

FDW扩展名适用数据源是否支持写入
postgres_fdwpostgres_fdw其他 PostgreSQL 实例是(INSERT/UPDATE/DELETE)
mysql_fdwmysql_fdwMySQL/MariaDB
file_fdwfile_fdwCSV/文本文件
s3_fdws3_fdwAWS S3 / 兼容对象存储
mongo_fdwmongo_fdwMongoDB
odbc_fdwodbc_fdw支持 ODBC 的数据源
tds_fdwtds_fdwSQL Server / Sybase

postgres_fdw 配置示例:

-- 1. 安装扩展
CREATE EXTENSION postgres_fdw;

-- 2. 创建外部服务器定义
CREATE SERVER pg_remote
  FOREIGN DATA WRAPPER postgres_fdw
  OPTIONS (host '10.0.1.100', port '5432', dbname 'appdb');

-- 3. 创建用户映射
CREATE USER MAPPING FOR local_user
  SERVER pg_remote
  OPTIONS (user 'remote_user', password 'secret');

-- 4. 创建外部表
CREATE FOREIGN TABLE remote_orders (
    id BIGSERIAL,
    customer_id BIGINT,
    amount NUMERIC(10,2),
    created_at TIMESTAMPTZ
)
SERVER pg_remote
OPTIONS (schema_name 'public', table_name 'orders');

-- 5. 查询(如同本地表)
SELECT * FROM remote_orders WHERE created_at > '2025-06-01';

FDW 性能优化实践:

优化手段说明配置
查询下推(Pushdown)将 WHERE 过滤下推到远端fetch_size 控制每次 fetch 行数
批量读取增大每次网络传输的行数fdw_startup_cost(影响 Planner 选择)
异步 Fetch并行预取下一批数据需使用 async_capable(PG14+)
连接复用同一会话复用远端连接默认复用
物化中间结果对于频繁查询的外部数据,使用 CREATE MATERIALIZED VIEW定期 REFRESH MATERIALIZED VIEW
16 PostgreSQL 的 JSONB 与全文搜索

答案:

PostgreSQL 提供 JSONB(二进制 JSON)和全文搜索(Full-Text Search, FTS)两种非结构化数据处理能力,均通过 GIN 索引加速。

JSONB 操作符:

操作符说明示例
->JSON 对象字段访问(返回 JSON)data->'name'
->>JSON 对象字段访问(返回文本)data->>'name'
#>路径访问(返回 JSON)data#>'{address,city}'
#>>路径访问(返回文本)data#>>'{address,city}'
@>包含(JSON 左侧是否包含右侧)data @> '{"status": "active"}'
<@被包含'{"status": "active"}' <@ data
?是否存在键data ? 'email'
`?`是否存在任一键
?&是否存在所有键data ?& ARRAY['name', 'age']
``
-删除键data - 'temporary_field'

JSONB 索引:

-- 默认 GIN 索引(支持 @>, ?, ?|, ?&)
CREATE INDEX idx_users_data ON users USING GIN (data);

-- 带路径的 GIN 索引(JSONPATH 查询优化,PG13+)
CREATE INDEX idx_users_data_path ON users USING GIN (data jsonb_path_ops);

-- B-Tree 索引用于 `->>` 操作(仅特定列)
CREATE INDEX idx_users_email ON users ((data->>'email'));

全文搜索(FTS)核心概念:

graph TD
    Raw["原始文档
'The quick brown fox jumps...'"] Parser["Parser
分词器"] Tokens["Token
'The', 'quick', 'brown', ..."] Dict["Dictionary
词典"] Raw --> Parser Parser --> Tokens Tokens --> Dict subgraph Dictionary["词典处理"] Stop["Stop Words 移除
(the, a, an, of, ...)"] Stem["Stemming 词干化
(jumps → jump)"] end Tokens --> Stop --> Stem Stem --> Tsvector["tsvector
'brown':3 'fox':4 'jump':6 'quick':2"]

FTS 配置示例:

-- 创建索引(to_tsvector + GIN)
CREATE INDEX idx_docs_content ON documents
  USING GIN (to_tsvector('english', content));

-- 搜索
SELECT title, ts_headline('english', content, query) AS highlight
FROM documents,
     plainto_tsquery('english', 'quick brown fox') AS query
WHERE to_tsvector('english', content) @@ query
ORDER BY ts_rank(to_tsvector('english', content), query) DESC
LIMIT 10;

-- 中文分词(需 zhparser 或 jieba 扩展)
CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);
ALTER TEXT SEARCH CONFIGURATION chinese
  ADD MAPPING FOR n,v,a,i,e,l WITH simple;

CREATE INDEX idx_docs_cn_content ON documents
  USING GIN (to_tsvector('chinese', content));

JSONB + FTS 组合示例:

-- 文档表(关系型 + JSONB + FTS)
CREATE TABLE documents (
    id BIGSERIAL PRIMARY KEY,
    metadata JSONB NOT NULL,           -- 结构化元数据
    content TEXT NOT NULL,             -- 全文内容
    created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_docs_metadata ON documents USING GIN (metadata);
CREATE INDEX idx_docs_fts ON documents
  USING GIN (to_tsvector('english', content));

-- 查询:结构化 + 全文混合检索
SELECT id, metadata->>'title' AS title
FROM documents
WHERE metadata @> '{"status": "published", "category": "tech"}'
  AND to_tsvector('english', content) @@ plainto_tsquery('english', 'distributed database')
  AND created_at > '2025-01-01';
17 PostgreSQL 的 Connection Pooling 原理

答案:

PostgreSQL 采用进程模型,每个连接对应一个独立的 Backend Process。在高并发场景下,连接数过多会导致内存耗尽和上下文切换开销剧增,必须引入连接池(Connection Pooling)机制。

无连接池的问题:

无连接池时:
  5000 个应用连接 → 5000 个 Backend Process
  每进程内存 = shared_buffers 中无此连接额外开销
  但:每进程 ~10MB 栈 + 私有内存
  5000 × 10MB ≈ 50GB 内存 → OOM
  
解决方案:
  sidecar 连接池 → 5000 个应用连接 → 连接池中的 50 个 Server 连接 → 50 个 Backend Process
  50 × 10MB = 500MB → 可接受

连接池架构对比:

连接池语言多线程协议特点
PgBouncerC单进程多线程PostgreSQL Wire Protocol轻量、稳定、事务池模式
Pgpool-IIC多进程PostgreSQL Wire Protocol读写分离 + 连接池 + 负载均衡
OdysseyC多线程PostgreSQL Wire Protocol高性能、查询路由、SCRAM
pgagroalC多线程PostgreSQL Wire Protocol高性能、内存安全
Application-Level应用语言JDBC/HikariCPHikariCP 适用于 Java

PgBouncer 池化模式:

模式行为适用场景是否支持 SET/LISTEN
session一个客户端连接占用一个服务器连接,持续到断开需要保持会话状态
transaction仅在事务期间占用服务器连接,事务结束即归还短事务、无状态服务
statement仅在单条语句期间占用(不跨语句)极端短查询
18 PostgreSQL 的配置参数调优框架

答案:

PostgreSQL 的配置参数根据生效范围分为三组:影响全局的 postgresql.conf 级别参数、影响当前会话的 Session 级参数、影响特定查询的 Query 级参数。

参数生效层级:

graph TD
    PG_CONF["postgresql.conf
(全局默认)"] CMD_LINE["命令行启动参数
-c shared_buffers=4GB"] DB_ALTER["ALTER DATABASE
(数据库级)"] USER_ALTER["ALTER ROLE
(用户级)"] SESSION_SET["SET statement
(会话级)"] PG_CONF --> CMD_LINE CMD_LINE --> DB_ALTER DB_ALTER --> USER_ALTER USER_ALTER --> SESSION_SET

参数优先顺序:SET > ALTER ROLE > ALTER DATABASE > postgresql.conf > 编译默认值。

分类调优参数:

内存参数:

参数推荐计算公式说明
shared_buffers物理内存 25%,上限 8GB数据页缓存
effective_cache_size物理内存 75%OS Page Cache 估计值,仅影响规划器
work_mem(内存 - shared_buffers) / (max_connections × 2)排序 / 哈希操作内存
maintenance_work_mem物理内存 10%,上限 2GBVACUUM / CREATE INDEX
wal_buffersshared_buffers 的 1/32,最小 64MBWAL 缓冲区

I/O 参数:

参数推荐值说明
random_page_costSSD: 1.1, HDD: 4.0随机页读取成本
effective_io_concurrencySSD: 200, HDD: 2并发 I/O 请求
wal_compressiononWAL 页压缩
wal_sync_methodfdatasyncWAL 刷盘方法
checkpoint_completion_target0.9Checkpoint 刷盘摊平
min_wal_size2GBWAL 空间下限
max_wal_size8GBWAL 空间上限

并发参数:

参数推荐值说明
max_connections配合连接池 ≤ 200最大客户端连接数
max_worker_processesCPU 核数 × 2后台 Worker 上限
max_parallel_workersCPU 核数并行 Worker 上限
max_parallel_workers_per_gatherCPU 核数 / 2单查询并行度
parallel_tuple_cost0.01并行元组传输成本
parallel_setup_cost1000并行启动成本

Autovacuum 参数:

参数推荐值说明
autovacuum_max_workers4~8最大 Worker 数
autovacuum_naptime30s~60s调度间隔
autovacuum_vacuum_cost_limit2000I/O 节流上限(提升可减少 VACUUM 延迟)
autovacuum_vacuum_cost_delay2msI/O 节流间隔
autovacuum_freeze_max_age2 亿防回卷 VACUUM 触发阈值
19 PostgreSQL 的 EXPLAIN 高级技巧与性能诊断

答案:

EXPLAIN 提供 PostgreSQL 查询性能诊断的第一手信息。结合 pg_stat_* 系统视图和 pg_stat_statements 扩展进行持续性能监控。

EXPLAIN (ANALYZE, BUFFERS, SETTINGS):

-- 完整诊断级 EXPLAIN
EXPLAIN (ANALYZE, BUFFERS, SETTINGS, TIMING, WAL)
SELECT o.*, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.created_at > '2025-01-01'
ORDER BY o.created_at DESC;

输出关键检查点:

检查项正常异常
rows vs actual rows偏差 < 2x偏差 > 10x → 统计信息过旧,执行 ANALYZE
Buffers: shared hit=100 read=1hit » read(95%+ 命中)read » hit → 缓存不足或全表扫描
Planning Time< 10ms> 100ms → 复杂视图/子查询膨胀
Execution Time符合预期异常高 → 检查 I/O 或锁等待
Workers LaunchedCPU 密集型查询启动并行并行未启动 → 检查 max_parallel_workers_per_gather

pg_stat_statements 查询分析:

-- 查看最耗时的前 10 条查询
SELECT
  queryid,
  LEFT(query, 80) AS query_short,
  total_exec_time / calls AS avg_time_ms,
  rows / calls AS avg_rows,
  shared_blks_hit * 100.0 / NULLIF(shared_blks_hit + shared_blks_read, 0) AS hit_ratio,
  calls
FROM pg_stat_statements
WHERE query NOT LIKE '%pg_stat%'
ORDER BY total_exec_time DESC
LIMIT 10;

等待事件分析(PG 等待事件体系):

-- 查看当前会话的等待事件
SELECT
  pid,
  wait_event_type,
  wait_event,
  state,
  query,
  AGE(clock_timestamp(), query_start) AS query_duration
FROM pg_stat_activity
WHERE state != 'idle'
  AND wait_event IS NOT NULL;
等待事件类型常见事件可能原因解决方向
Lockrelation、transactionid锁冲突、死锁等待检查 pg_locks,优化事务顺序
IODataFileRead、WALWrite磁盘 I/O 瓶颈、shared_buffers 不足检查 I/O 延迟,增加 shared_buffers
ClientClientRead、ClientWrite应用端慢,网络延迟检查应用和网络
ActivityArchiverMain、BgWriterMain后台进程正常活动无需处理
TimeoutPgSleeppg_sleep() 调用检查应用逻辑

索引使用诊断:

-- 检查未使用的索引
SELECT
  schemaname,
  tablename,
  indexname,
  idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
  AND indexrelid NOT IN (
    SELECT indexrelid FROM pg_constraint WHERE conindid = indexrelid
  );

-- 检查冗余索引
SELECT
  pg_size_pretty(SUM(pg_relation_size(indexrelid))) AS total_size
FROM pg_index
WHERE indrelid = 'orders'::regclass;
20 PostgreSQL 的扩展生态(Contrib Extensions)

答案:

PostgreSQL 拥有丰富的扩展生态,覆盖功能增强、性能诊断、数据集成、监控发布等场景。

必装扩展:

扩展功能启用方式
pg_stat_statements查询性能统计shared_preload_libraries
pg_buffercacheShared Buffers 内容分析CREATE EXTENSION
pgstattuple表/索引膨胀分析CREATE EXTENSION
pg_visibility可见性映射(VM)分析CREATE EXTENSION
pageinspect数据页内容查看CREATE EXTENSION
pg_walinspectWAL 内容分析(PG13+)CREATE EXTENSION
auto_explain自动记录慢查询执行计划shared_preload_libraries
pg_prewarm预热缓存CREATE EXTENSION
bloomBloom Filter 索引CREATE EXTENSION
postgres_fdw外部表访问CREATE EXTENSION

功能扩展:

扩展功能适用场景
uuid-ossp / pgcryptoUUID 生成主键、分布式 ID
pg_trgm相似度搜索(模糊匹配)LIKE '%something%' 的 GIN 索引加速
fuzzystrmatch字符串近似匹配soundex()levenshtein()metaphone()
hstore键值对存储(JSONB 前身)简单键值数据
ltree树状路径数据类型分类/目录层级
citext大小写不敏感字符串用户搜索、标签
pg_partman分区表自动管理时间序列分区维护
pgaudit审计日志合规审计需求
pgfincore分析 Page Cache 命中性能诊断
postgis地理空间数据GIS 应用

auto_explain 配置:

-- postgresql.conf
shared_preload_libraries = 'pg_stat_statements, auto_explain'

-- auto_explain 参数
auto_explain.log_min_duration = '1s'    -- 记录超过 1s 的查询
auto_explain.log_analyze = on           -- 输出 ANALYZE 信息
auto_explain.log_buffers = on           -- 输出 Buffer 信息
auto_explain.log_nested_statements = on -- 记录嵌套语句
auto_explain.log_timing = on            -- 记录时间
auto_explain.log_verbose = on           -- 输出完整信息
auto_explain.sample_rate = 1.0          -- 采样率(生产可设 0.1)

PostGIS:

CREATE EXTENSION postgis;

-- 创建空间表
CREATE TABLE locations (
    id BIGSERIAL PRIMARY KEY,
    name TEXT,
    geom GEOMETRY(Point, 4326)  -- WGS84 经纬度坐标系
);

CREATE INDEX idx_locations_geom ON locations USING GIST (geom);

-- 距离查询
SELECT name FROM locations
WHERE ST_DWithin(geom, ST_MakePoint(116.4074, 39.9042), 0.05);
-- 经纬度 (116.4, 39.9) 附近约 5km 的位置