Skip to content

PostgreSQL 架构概览 ​

PostgreSQL 是一个客户端-服务器(Client-Server)架构的关系型数据库。本节从整体视角俯瞰它的进程、内存与存储三大组成部分,讲清它与 SQLite 这类嵌入式数据库的本质差异,以及「一个实例管理多个数据库」的单机多库结构,为后续深入进程模型、内存结构与磁盘结构打下全局认知。

前置知识

阅读本节前,建议先了解:SSL 连接初始化

基础概念 ​

要理解 PostgreSQL 的架构,先要理清一组容易混淆的名词。它们的层级关系是:实例(Instance)→ 集群(Cluster)→ 数据库(Database)→ 模式(Schema)→ 对象(Object)。

术语英文名说明
实例Instance一个运行中的服务端,由一组进程 + 一块共享内存构成,监听固定端口
集群Cluster一个数据目录(PGDATA)下的全部数据;一个实例对应一个集群
数据库Database集群内的逻辑隔离单元,彼此不能跨库查询(除非用 dblink / FDW)
模式Schema数据库内的命名空间,默认 public
表空间Tablespace数据文件的物理存放位置,可跨磁盘
后端进程Backend Process每个客户端连接对应一个服务端进程

术语陷阱

PostgreSQL 官方文档中「集群(Cluster)」指的是一个数据目录,而不是「多台机器组成的集群」。多机方案叫「复制(Replication)」或「高可用(High Availability)」。初学时务必区分,否则读文档会严重误解。

PostgreSQL 采用进程(Process)模型而非线程模型:每条客户端连接都由一个独立的操作系统进程服务。这与 MySQL 的「单进程 + 多线程」形成鲜明对比。

三大组成部分 ​

整个 PostgreSQL 实例可以拆解为三块:

组成内容生命周期
进程(Processes)主进程 Postmaster、后端进程、后台辅助进程(bgwriter、walwriter、checkpointer、autovacuum 等)服务启动到停止
内存(Memory)共享内存区(shared_buffers、WAL buffer、CLOG、锁表)+ 每进程本地内存(work_mem、temp_buffers 等)共享内存随实例;本地内存随会话
存储(Storage)数据目录 PGDATA 下的表文件、索引文件、WAL 文件、事务状态文件等持久化

一个典型生产实例的组件协作关系如下:

text
┌───────────────────────────────────────────────────────────────┐
│                      客户端(libpq / JDBC / psql)              │
└───────────────────────────┬───────────────────────────────────┘
                            │ TCP 5432 / Unix Socket
┌───────────────────────────▼───────────────────────────────────┐
│                     Postmaster 主进程                          │
│          (监听连接、派生后端、监控子进程、崩溃恢复)             │
└──────┬────────────────────────────────────┬───────────────────┘
       │ fork                               │ fork
┌──────▼──────────┐              ┌──────────▼────────────────────┐
│  Backend 后端    │              │  后台辅助进程(Background)      │
│  每连接一进程     │              │  bgwriter / walwriter /        │
│  解析、计划、执行 │              │  checkpointer / autovacuum /   │
│                 │              │  archiver / walsender ...      │
└──────┬──────────┘              └──────────┬────────────────────┘
       │                                    │
┌──────▼────────────────────────────────────▼───────────────────┐
│                        共享内存(Shared Memory)                │
│  shared_buffers │ WAL buffers │ CLOG │ lock table │ proc array  │
└──────┬────────────────────────────────────────────────────────┘
       │ 读写
┌──────▼────────────────────────────────────────────────────────┐
│                    PGDATA 数据目录(磁盘)                      │
│  base/ │ global/ │ pg_wal/ │ pg_xact/ │ pg_tblspc/ │ postgresql.conf │
└───────────────────────────────────────────────────────────────┘

与嵌入式数据库的区别 ​

SQLite 常被拿来与 PostgreSQL 对比,但二者架构截然不同:

维度PostgreSQL(客户端-服务器)SQLite(嵌入式)
运行形态独立服务进程,客户端通过网络连接一个库文件,进程内直接读写
并发模型多进程 + MVCC,读写不阻塞文件锁,写串行
网络访问原生支持,多客户端共享无网络层,需自己封装
权限体系角色、模式、行级安全(RLS)无(依赖文件系统权限)
事务隔离完整 ACID + 多种隔离级别ACID,写并发受限
典型场景后端服务、多用户、大数据量移动端、桌面应用、单机工具

选择的关键在于:是否需要多客户端并发、是否需要网络访问、是否需要细粒度权限。三者只要有一个是「是」,就应选 PostgreSQL。

为什么 PostgreSQL 不用线程

PostgreSQL 诞生于 1986 年,当时 Unix 的线程实现尚不成熟且不可移植,因此选择多进程。多进程的好处是隔离性强——某个后端进程崩溃不会拖垮整个实例(Postmaster 会检测到并重建共享内存);代价是进程创建开销高于线程,且共享内存只能通过内核共享内存段传递。这也是连接池在 PostgreSQL 中尤其重要的原因。

核心机制 ​

客户端-服务器通信 ​

PostgreSQL 使用自己定义的前端/后端协议(Frontend/Backend Protocol)在 TCP 或 Unix 域套接字上通信。协议是消息(Message)驱动的:客户端发送 Query、Parse、Bind 等消息,服务端回复 RowDescription、DataRow、CommandComplete、ReadyForQuery 等消息。

传输通道配置项说明适用
TCP/IPlisten_addresses、port可跨主机访问,需 pg_hba.conf 授权应用服务器连接
Unix 域套接字unix_socket_directories仅本机,路径即身份,性能略优本机管理、同机应用
SSL/TLSssl、证书文件在 TCP 之上加密跨网络安全连接
sql
-- 查看服务端监听与连接相关配置
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name IN ('listen_addresses', 'port', 'unix_socket_directories',
               'max_connections', 'superuser_reserved_connections')
ORDER BY name;
text
            name             |        setting         | unit |                     short_desc
-----------------------------+------------------------+------+----------------------------------------------------
 listen_addresses            | localhost              |      | Sets the host name or IP address(es) to listen to.
 max_connections             | 100                    |      | Sets the maximum number of concurrent connections.
 port                        | 5432                   |      | Sets the TCP port the server listens on.
 superuser_reserved_connections | 3                   |      | Sets the number of connections reserved for superusers.
 unix_socket_directories     | /var/run/postgresql    |      | Sets the directories where Unix-domain sockets will be c
(5 rows)

superuser_reserved_connections

max_connections = 100 并不意味着普通用户能用满 100 个连接。其中 superuser_reserved_connections(默认 3)个连接被保留给超级用户,防止 DBA 在连接耗尽时无法登录救火。

单机多库结构 ​

一个 PostgreSQL 实例(一个数据目录)内可以创建多个数据库。这一点与 MySQL 不同:MySQL 中 database 约等于 schema,而 PostgreSQL 中 database 与 schema 是两级独立的概念。

sql
-- 列出当前实例中的所有数据库
SELECT datname,
       pg_size_pretty(pg_database_size(datname)) AS size,
       datallowconn,
       datistemplate
FROM pg_database
ORDER BY pg_database_size(datname) DESC;
text
   datname   |  size   | datallowconn | datistemplate
-------------+---------+--------------+---------------
 demo        | 4832 MB | t            | f
 postgres    | 8621 kB | t            | f
 template1   | 7601 kB | t            | t
 template0   | 7489 kB | f            | t
(4 rows)

关键规则:

  • 不能跨库查询:连接到 demo 库时,无法在一条 SQL 里访问 postgres 库的表。这是与 MySQL db.table 语法的最大差异。
  • template0 不可连接:它作为「干净的原始模板」,datallowconn = f,用于以指定编码创建新库。
  • template1 是默认模板:CREATE DATABASE newdb 实际是复制 template1。往 template1 里装扩展会让所有新库都带上它。
  • 系统目录是全局共享的一部分:pg_database、pg_authid 等共享目录存放在 global/ 目录,所有库共用;而 pg_class、pg_attribute 等是每库一份。
sql
-- 数据库内的 schema 层次:一个库下可挂多个 schema
SELECT nspname AS schema_name,
       pg_get_userbyid(nspowner) AS owner
FROM pg_namespace
WHERE nspname NOT LIKE 'pg_%'
ORDER BY nspname;
text
 schema_name |  owner
-------------+----------
 public      | postgres
 app         | app_user
 reporting   | analyst
(3 rows)

对象的完整限定名是 database.schema.object,但由于同库内才能引用,实际使用时写作 schema.object:

sql
-- 限定名引用,避免依赖 search_path
SELECT count(*) FROM app.orders;

-- 跨库访问必须借助扩展(dblink / postgres_fdw),不能直接 JOIN
-- 例如 postgres_fdw:
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE SERVER remote_db FOREIGN DATA WRAPPER postgres_fdw
  OPTIONS (host '10.0.2.11', port '5432', dbname 'reporting');

详细说明 ​

进程组成一览 ​

启动一个 PostgreSQL 实例后,用 ps 可以看到完整的进程树。所有进程都以主进程为父进程:

bash
# 查看实例的全部进程(RPM/YUM 安装路径为 /usr/pgsql-18/bin/postgres)
ps -ef | grep '[p]ostgres'
text
postgres  1147       1  0 09:12 ?  00:00:03 /usr/lib/postgresql/18/bin/postgres -D /var/lib/postgresql/18/main
postgres  1149    1147  0 09:12 ?  00:00:00   postgres: checkpointer
postgres  1150    1147  0 09:12 ?  00:00:01   postgres: background writer
postgres  1152    1147  0 09:12 ?  00:00:01   postgres: walwriter
postgres  1153    1147  0 09:12 ?  00:00:00   postgres: autovacuum launcher
postgres  1154    1147  0 09:12 ?  00:00:00   postgres: logical replication launcher
postgres  1178    1147  0 09:20 ?  00:00:00   postgres: postgres postgres [local] idle
postgres  1201    1147  0 09:22 ?  00:00:00   postgres: app appdb 10.0.1.5(51234) idle
进程类型职责后续章节
postgres -D ...主进程监听、派生、监控、崩溃恢复Postmaster 主进程
checkpointer后台定期写检查点,缩短恢复时间Checkpointer
background writer后台提前刷脏页,平滑 I/OBackground Writer
walwriter后台把 WAL 缓冲刷入磁盘WAL Writer
autovacuum launcher后台调度 autovacuum workerAutoVacuum Worker
logical replication launcher后台启动逻辑复制工作进程—(复制章节)
postgres: <user> <db> <host>后端处理一条客户端连接Backend 后端进程

从命令行识别进程类型

ps 输出中的 postgres: 前缀后的内容就是进程的「命令行标签」。客户端后端会显示 <用户名> <数据库名> <来源地址>,而辅助进程只显示名称。这是排查时最快的分类依据。

PostgreSQL 10 之前,辅助进程的名字与现在不同(如 wal writer process),升级后 ps 输出格式变化属于正常现象。

内存两级结构总览 ​

内存分为共享内存(Shared Memory)与本地内存(Local Memory)两级:

层级分配时机内容是否所有进程可见
共享内存实例启动时一次性分配shared_buffers、WAL buffers、CLOG buffer、锁表、进程数组是
本地内存会话/操作开始时work_mem、maintenance_work_mem、temp_buffers、catalog cache否,进程私有
sql
-- 关键内存参数速查(setting 单位见 unit 列)
SELECT name, setting, unit, context
FROM pg_settings
WHERE name IN ('shared_buffers', 'wal_buffers', 'work_mem',
               'maintenance_work_mem', 'temp_buffers', 'effective_cache_size')
ORDER BY name;
text
          name          | setting | unit |  context
------------------------+---------+------+-----------
 effective_cache_size   | 524288  | 8kB  | user
 maintenance_work_mem   | 65536   | kB   | user
 shared_buffers         | 16384   | 8kB  | postmaster
 temp_buffers           | 1024    | 8kB  | user
 wal_buffers            | -1      | 8kB  | postmaster
 work_mem               | 4096    | kB   | user
(6 rows)

shared_buffers = 16384 的单位是 8KB 块,即 128MB(16384 × 8KB)。wal_buffers = -1 表示「自动」,由服务端按 shared_buffers 推算(约 1/32,上限 16MB)。

存储目录总览 ​

PGDATA 目录下既有数据文件,也有配置文件与状态文件:

目录 / 文件内容是否可跨库共享
base/每个数据库一个以 OID 命名的子目录,存放表与索引文件否,每库独立
global/全局共享的系统目录(pg_database、pg_authid 等)与 pg_control是
pg_wal/WAL 预写日志段文件是
pg_xact/事务提交状态(CLOG)文件是
pg_tblspc/用户自定义表空间的符号链接是
pg_multixact/多事务(MultiXact)状态文件是
postgresql.conf主配置文件(Debian 系除外)—
pg_hba.conf认证规则—
PG_VERSION数据目录格式版本号—
bash
# 观察典型 PGDATA 目录
ls -1 /var/lib/postgresql/18/main/
text
PG_VERSION
base
global
pg_commit_ts
pg_dynshmem
pg_hba.conf
pg_ident.conf
pg_logical
pg_multixact
pg_notify
pg_replslot
pg_serial
pg_snapshots
pg_stat
pg_stat_tmp
pg_subtrans
pg_tblspc
pg_twophase
pg_wal
pg_xact
postgresql.auto.conf
postgresql.conf

PGDATA 权限

PGDATA 目录权限必须为 0700 且属主为运行 PostgreSQL 的系统用户。PostgreSQL 启动时会检查,权限过宽会拒绝启动(报 data directory has invalid permissions)。这是为了保护数据文件不被其他系统用户直接读取——数据库内的权限控制无法防御绕过服务端的文件级访问。

架构演进时间线(版本相关) ​

PostgreSQL 的核心架构多年来保持稳定,但有几个关键变化需要知道:

版本架构变化
9.6引入并行查询,后端进程可派生并行工作进程
10逻辑复制落地;pg_wal 从 pg_xlog 改名
12引入 pg_stat_progress_* 进度视图;分区表性能大幅改进
13移除 pg_stat_tmp 的统计临时目录依赖
14引入 pg_stat_wal;B-tree 索引去重
15移除 stats collector 进程,改为共享内存统计;引入 pg_stat_io(16)
16引入 pg_stat_io 视图,I/O 统计按 backend type 细分
17pg_stat_bgwriter 拆分出 pg_stat_checkpointer、pg_stat_io 增强
sql
-- 确认当前实例版本与关键编译选项(架构相关能力由编译期决定)
SELECT version();

SELECT name, setting
FROM pg_settings
WHERE name IN ('block_size', 'wal_block_size', 'segment_size',
               'max_connections', 'max_worker_processes', 'max_parallel_workers')
ORDER BY name;
text
PostgreSQL 18.0 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 13.2.0, 64-bit

          name          | setting
------------------------+---------
 block_size             | 8192
 max_connections        | 100
 max_parallel_workers   | 8
 max_worker_processes   | 8
 segment_size           | 1GB
 wal_block_size         | 8192
(6 rows)

block_size 与 segment_size 是 internal 上下文参数——只能在编译时指定,运行期不可更改(./configure --with-blocksize=8)。默认 8KB 页与 1GB 段文件是几乎所有生产环境的实际选择。

实战示例 ​

示例一:绘制你手上实例的架构全貌 ​

sql
-- 一次性输出:版本、数据目录、关键资源参数
SELECT version()                                               AS version,
       current_setting('data_directory')                       AS data_dir,
       current_setting('config_file')                          AS config_file,
       pg_size_pretty(pg_size_bytes(current_setting('shared_buffers'))) AS shared_buffers,
       current_setting('max_connections')                      AS max_conn,
       current_database()                                      AS current_db,
       pg_backend_pid()                                        AS my_backend_pid;
text
-[ RECORD 1 ]--+-------------------------------------------------------------
version        | PostgreSQL 18.0 on x86_64-pc-linux-gnu, compiled by gcc 13.2.0, 64-bit
data_dir       | /var/lib/postgresql/18/main
config_file    | /etc/postgresql/18/main/postgresql.conf
shared_buffers | 128 MB
max_conn       | 100
current_db     | demo
my_backend_pid | 1201
sql
-- 统计当前实例的进程类型分布
SELECT backend_type,
       count(*)                       AS cnt,
       string_agg(DISTINCT coalesce(usename, '-'), ', ') AS users
FROM pg_stat_activity
GROUP BY backend_type
ORDER BY cnt DESC;
text
     backend_type     | cnt |         users
----------------------+-----+-----------------------
 client backend       |   6 | app_user, postgres
 checkpointer         |   1 | -
 background writer    |   1 | -
 walwriter            |   1 | -
 autovacuum launcher  |   1 | -
 logical replication launcher | 1 | -
(6 rows)

示例二:验证「不能跨库查询」 ​

bash
# 在 demo 库中尝试访问 postgres 库的表 → 报错
psql -d demo -c "SELECT count(*) FROM postgres.pg_catalog.pg_class;"
text
ERROR:  cross-database references are not implemented: "postgres.pg_catalog.pg_class"
LINE 1: SELECT count(*) FROM postgres.pg_catalog.pg_class;
sql
-- 正确做法一:切换到目标库
-- \c 是 psql 元命令,不是 SQL
-- \c postgres
-- SELECT count(*) FROM pg_catalog.pg_class;

-- 正确做法二:用 postgres_fdw 做跨库联邦查询
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE SERVER pg_self FOREIGN DATA WRAPPER postgres_fdw
  OPTIONS (host 'localhost', port '5432', dbname 'reporting');
CREATE USER MAPPING FOR CURRENT_USER SERVER pg_self
  OPTIONS (user 'postgres');
CREATE FOREIGN TABLE ft_report (
  id   bigint,
  name text
) SERVER pg_self OPTIONS (schema_name 'public', table_name 'report');

SELECT * FROM ft_report LIMIT 5;

同一实例内的跨库查询也可以用 postgres_fdw

即使两个库在同一个实例、同一个数据目录,也不能用 db.schema.table 直接引用。必须通过 postgres_fdw、dblink,或者把对象放到同一个库的不同 schema 里。

示例三:从进程角度确认「每连接一进程」 ​

bash
# 开 3 个后台 psql 会话(保持连接)
for i in 1 2 3; do
  (psql -d demo -c "SELECT pg_sleep(60);" >/dev/null 2>&1 &)
done
sleep 1

# 统计 client backend 进程数
ps -ef | grep 'postgres: ' | grep -v grep | wc -l
sql
-- 从数据库内部对照:每条连接一个 pid
SELECT pid,
       backend_start,
       state,
       coalesce(client_addr::text, 'local') AS client,
       application_name
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY backend_start;
text
 pid  |         backend_start         | state  | client  | application_name
------+-------------------------------+--------+---------+------------------
 1178 | 2026-09-22 09:20:11.482931+08 | idle   | local   | psql
 1201 | 2026-09-22 09:22:03.117204+08 | active | 10.0.1.5| psql
 1215 | 2026-09-22 09:31:44.902117+08 | active | local   | psql
 1216 | 2026-09-22 09:31:44.903551+08 | active | local   | psql
 1217 | 2026-09-22 09:31:44.904882+08 | active | local   | psql
(5 rows)

pg_stat_activity.pid 与操作系统 ps 中的进程号一一对应,这就是「每连接一进程」最直接的证据。

注意事项 ​

  • 「集群」不是「分布式集群」:PostgreSQL 文档里的 Cluster 指一个数据目录,不代表多机。多机要看复制与高可用章节。
  • 同实例跨库不能直接查询:db.schema.table 语法不存在,必须切换连接或使用 postgres_fdw / dblink。
  • template1 会被复制:在 template1 里创建对象或安装扩展,会影响此后所有 CREATE DATABASE 的结果。保持 template1 干净是最佳实践。
  • 连接数不是越高越好:每条连接一个进程,且各占约 5~10MB 本地内存。max_connections = 1000 的实例在连接风暴下可能因内存与上下文切换而雪崩。生产应配合连接池。
  • PGDATA 权限必须为 0700:权限过宽会导致启动失败,且数据文件可被同机其他用户直接读取,绕过所有数据库权限控制。
  • block_size 与 segment_size 不可运行期修改:需要重新编译,不要轻信「改参数就能调页大小」的说法。
  • 不要用 kill -9 杀掉后端进程:这会导致共享内存状态不一致,Postmaster 会触发全体重启。要终止查询请用 pg_cancel_backend() / pg_terminate_backend()。
  • 超级用户保留连接:superuser_reserved_connections 一定要留有余量,否则连接打满后无法登录排查。
sql
-- 不推荐:把 max_connections 调到极大值来解决「连接不够」
-- ALTER SYSTEM SET max_connections = 2000;   -- 不推荐
-- 正确做法:引入连接池(PgBouncer),让应用侧连接数远小于数据库侧的并发需求

最佳实践 ​

  • 架构认知先行:排查性能问题前,先明确「这是连接数问题、内存问题还是 I/O 问题」,分别对应进程/内存/存储三个维度。
  • 为不同用途划分数据库:生产库与报表库分实例或分库,避免分析型查询抢占事务型资源。
  • 用 schema 做逻辑隔离:多租户场景优先用 schema(tenant_a.orders),比「每租户一个数据库」更省连接与维护成本。
  • monitoring 与业务分离:监控账号用独立的数据库连接,并保留 superuser_reserved_connections 应急通道。
  • 把关键参数纳入配置管理:shared_buffers、max_connections、wal_level 等属于架构级决策,应写入配置文件并纳入版本控制,而不是用 ALTER SYSTEM 临时设置。
  • 定期巡检进程与连接:把 pg_stat_activity 的连接数、空闲事务数纳入监控告警。
  • 理解内存两级结构再调优:shared_buffers 是全局的(重启生效),work_mem 是每操作的(可动态改),二者误用会导致 OOM。
  • 升级前阅读版本说明:架构相关的视图与进程名在版本间会变化(如 PG15 移除 stats collector、PG17 拆分 pg_stat_bgwriter),升级脚本与监控看板需要同步更新。

参考链接 ​