3.PostgreSQL的核心架构

PostgreSQL的核心架构

进程及内存结构

进程和内存架构

启动PostgreSQL数据库时,会先启动一个叫Postmaster的主进程,还会fork出一些辅助子进程,这些辅助子进程各自负责一部分功能。辅助子进程的分类如下。

  • ·Logger(系统日志)进程。
  • ·BgWriter(后台写)进程。
  • ·WalWriter(预写式日志)进程。
  • ·PgArch(归档)进程。
  • ·AutoVacuum(系统自动清理)进程。
  • ·PgStat(统计信息收集)进程。

后面会详细讲解这些进程的作用。

主进程Postmaster介绍

PostgreSQL数据库的主要功能都集中于Postgres程序,该程序位于安装目录的bin目录下,命令如下:

osdba@osdba-laptop:~$ which postgres
/usr/local/pgsql/bin/postgres
osdba@osdba-laptop:~$ ls -l /usr/local/pgsql/bin/postgres
-rwxr-xr-x 1 root root 5964188 7月 8 22:35
/usr/local/pgsql/bin/postgres

主进程Postmaster是整个数据库实例的总控进程,负责启动和关闭该数据库实例。用户可以运行postmaster、postgres命令并加上合适的参数来启动数据库。实际上,postmaster命令是一个指向Postgres的链接,示例如下:

osdba@osdba-laptop:~$ ls -l
/usr/local/pgsql/bin/postmaster
lrwxrwxrwx 1 root root 8 7月 8 22:35
/usr/local/pgsql/bin/postmaster -> postgres

当然,更多的时候我们使用pg_ctl来启动数据库,pg_ctl也是通过运行postgres命令来启动数据库的,只是它做了一些包装,让我们更容易启动数据库。

所以,主进程Postmaster实际上是第一个Postgres进程,此主进程还会fork出一些与数据库实例相关的辅助子进程,并对其进行管理。

当用户与PostgreSQL数据库建立连接时,实际上是先与Postmaster进程建立连接,此时,客户端程序会发出身份验证消息给Postmaster主进程,Postmaster主进程根据消息中的信息进行客户端身份验证,如果验证成功,Postmaster主进程会fork出一个子进程来为该连接服务。fork出的子进程称为服务进程。查询pg_stat_activity表时看到的PID就是这些服务进程的PID,命令如下:

postgres=# select pid,usename,client_addr,client_port
from pg_stat_activity;
pid | usename | client_addr | client_port
------+---------+-------------+-------------
3753 | osdba | | -1
3766 | osdba | | -1
3768 | osdba | | -1
(3 rows)

因为每次客户端与数据库建立连接时,PostgreSQL数据库都会启动一个服务进程来为该连接服务,所以PostgreSQL数据库是进程架构模型,这与MySQL数据库是不一样的,MySQL数据库每建立一个连接时启动的是一个线程,所以MySQL数据库是线程架构模型

Logger系统日志进程介绍

在配置文件postgresql.conf中有很多与日志相关的参数,这些参数后面会介绍。其中,只有在参数logging_collect设置为“on”时,主进程才会启动Logger辅助进程。

BgWriter后台写进程介绍

在PostgreSQL中,BgWriter辅助进程是把共享内存中的脏页写到磁盘上的进程。

当向数据库中插入或更新数据时,并不会马上把数据持久化到数据文件中。这主要是为了提高插入、更新、删除数据的性能。BgWriter辅助进程可周期性地把内存中的脏数据刷新到磁盘中,刷新频率既不能太快,也不能太慢。如果一个数据块被改变了多次,而此时刷新频率又太快,那么这些改变每次都会被保存到磁盘中,这会导致I/O次数增多。

在刷新频率太慢的情况下,若有新的查询或更新需要使用内存来保存从磁盘中读取的数据块时,由于没有空闲空间来存储这些数据块,就需要把内存腾出来,即先把一些内存中的脏页写
到磁盘中,这样就会导致查询或更新需要等更长的时间,自然就会降低性能。上面提到的这些机制由以“bgwriter_”开头的配置参数来控制,后面的章节会详细介绍这些参数的作用。

WalWriter预写式日志写进程介绍

WAL是Write Ahead Log的缩写,中文名称为“预写式日志”。WAL日志也被简称为“xlog”。WalWriter进程就是写WAL日志的进程

预写式日志的概念就是,在修改数据之前必须把这些修改操作记录到磁盘中,这样后面更新实际数据时就不需要实时地把数据持久化到文件中了。

如果机器突然宕机或数据库异常退出,导致一部分内存中的脏数据没有及时地刷新到文件中,在数据库重启后,通过读取WAL日志把最后一部分的WAL日志重新执行一遍,就可以将数据库恢复到宕机时的状态。

WAL日志保存在pg_xlog下。xlog文件的默认大小是16MB,为了满足恢复要求,在xlog目录下会产生多个WAL日志,这样就可以保证在宕机后,未持久化的数据都可以通过WAL日志来恢复,那些不需要的WAL日志会被自动覆盖。

PgArch归档进程

WAL日志会循环使用,也就是说,较早期的WAL日志会被覆盖。PgArch归档进程会在覆盖前把WAL日志备份出来。

PostgreSQL从8.X版本开始提供PITR(Point-In-Time-Recovery)技术,通俗来讲,就是在对数据库进行过一次全量备份后,使用该技术将备份时间点之后的WAL日志通过归档进行备份,使用数据库的全量备份再加上后面产生的WAL日志,即可把数据库向前回滚到全量备份后的任意一个时间节点了。

AutoVacuum自动清理进程

在PostgreSQL数据库中,对表进行DELETE操作后,原有数据并不会立即被删除,而且在更新数据时,也并不会在原有数据上做更新,而是会新生成一行数据。这在前面“锁”相关章节中已经介绍,我们称之为“多版本”。此时,原有数据只是被标识为删除状态,只有在没有并发的其他事务读到这些旧数据时,才会将其清除。这个清除工作就是由AutoVacuum进程来完成的。

PgStat统计数据收集进程

PgStat辅助进程主要做数据的统计收集工作。收集的信息主要用于查询优化时的代价估算,这些信息包括在一个表和索引上进行了多少次插入、更新、删除操作,磁盘块读写的次数,以及行的读次数。系统表pg_statistic中存储了PgStat收集的各类统计信息

共享内存

PostgreSQL启动后会生成一块共享内存,共享内存主要用作数据块的缓冲区,以便提高读写性能。WAL日志缓冲区和CLOG(CommitLog)缓冲区也存在于共享内存中。除此以外,一些全局信息也保存在共享内存中,如进程信息、锁的信息、全局统计信息等。

PostgreSQL 9.3之前的版本与Oracle数据库一样,都是使用SystemⅤ类型的共享内存,但自PostgreSQL 9.3版本之后,PostgreSQL使用mmap()方式的共享内存。使用这种共享内存的好处是无须再配置SystemⅤ共享内存的内核参数kernel.shmmax和kernel.shmall,就能使用较大的共享内存。

  • 共享内存中的内容
    共享内存

本地内存

后台服务进程除访问共享内存以外,还会申请分配一些本地内存,以便暂存一些不需要全局存储的数据。这些内存缓冲区主要有以下几个:

  • ·临时缓冲区:用于访问临时表的本地缓冲区。
  • ·work_mem:内部排序操作和Hash表在使用临时磁盘文件之前使用的内存缓冲区。
  • ·maintenance_work_mem:在维护性操作(比如VACUUM、CREATE INDEX和ALTER TABLE ADD FOREIGN KEY等)中使用的内存缓冲区。

存储结构

PostgreSQL 数据目录下的主要文件类型

  • 控制类
• postgresql.conf / postgresql.auto.conf —— 主配置文件
• pg_hba.conf —— 客户端认证配置
• pg_ident.conf —— 用户映射配置
  • 全局系统表 & 元数据
• PG_VERSION —— 版本号
• pg_control —— 控制文件:记录系统标识、检查点、WAL 位置等,启动时必须读取
  • 数据库文件(每库一个子目录)
• base/ 目录 —— 每个数据库对应一个 OID 子目录,里面存放真正的表/索引数据文件(默认 1 GB 分片,命名形如 12345.1)。
• global/ 目录 —— 存放共享系统表(如 pg_database、pg_authid)。
  • 事务提交日志
• pg_xact/(旧版叫 pg_clog)—— 记录事务提交状态(运行中、已提交、已回滚)。
  • 预写日志(WAL)
• pg_wal/(旧版叫 pg_xlog)—— WAL 段文件,命名形如 0000000100000000000000A1;所有修改先写 WAL 再刷数据页。
  • 日志文件
• pg_log/ 或 log/ —— 错误日志、CSV 日志(由 log_destination 控制)。
  • 统计与临时文件
• pg_stat_tmp/ —— 运行时统计信息
• base/pgsql_tmp* —— 排序、哈希等操作产生的临时文件
  • 复制/备份相关
• pg_replslot/ —— 逻辑/物理复制槽
• pg_archive/ —— 归档脚本或归档目录(自定义)
• backup_label / tablespace_map —— 在线备份时自动生成

逻辑存储结构

在PostgreSQL中,逻辑对象是有层次关系的,数据库创建后,有一个叫数据库簇的概念,虽然数据库簇的英文为database cluster,但它并不是数据库集群的意思,故而翻译为数据库簇。在数据库簇中可以创建很多数据库(使用create database创建),也就是说,数据库簇相当于是一个数据库的容器。而PostgreSQL中的database与MySQL中的Database完全不是一个概念,PostgreSQL的Database是一个多租户的概念,与Oracle 12C的Pluggable Database类似,主要实现租户隔离。在Database下,可以有多个模式(Schema),数据库逻辑存储
结构的层次关系如图7-3所示。
逻辑对象层次关系

软件目录结构

安装目录

物理存储结构

一般使用环境变量PGDATA指向数据目录的根目录。该目录是在安装时指定的,所以在安装时需要指定一个合适的目录作为数据目录的根目录,而且每一个数据库实例都需要一个根目录。目录的初使化是使用initdb来完成的,初始化完成后,数据根目录下就会生成以下6个配置文件。

·postgresql.conf:此数据库实例的主配置文件,基本上所有的配置参数都在此文件中。
·postgresql.auto.conf:使用ALTER SYSTEM修改的配置参数存储在该文件中(PostgreSQL 9.4及更高版本)。
·pg_hba.conf:认证配置文件,用于配置允许哪些IP的主机访问数据库、认证的方法是什么等信息。
·pg_ident.conf:ident认证方式的用户映射文件。
·PG_VERSION:存储PostgreSQL主版本号。
·postmaster.opts:记录服务器上次启动的命令行参数。·此目录下还会生成如下子目录。
·base:默认表空间的目录。
·global:一些共享系统表的目录。
·log:程序日志目录,在查询一些系统错误时可查看此目录下的日志文件。在10版本之前此目录是“pg_log”。
·pg_commit_ts:视图提交的时间戳数据(PostgreSQL 9.5及更高版本)。
·pg_dynshmem:动态共享内存子系统使用的文件(PostgreSQL9.4及更高版本)。
·pg_logical:逻辑复制的状态数据(PostgreSQL 9.4及更高版本)。
·pg_multixact:多事务状态数据。
·pg_notify:LISTEN/NOTIFY状态数据。
·pg_repslot:复制槽数据(PostgreSQL 9.4及更高版本)。
·pg_serial:已提交的可串行化事务相关信息(PostgreSQL 9.1及更高版本)。
·pg_snapshot:PostgreSQL函数“pg_export_snapshot”导出的快照信息文件(PostgreSQL 9.2及更高版本)。
·pg_stat:统计子系统的永久文件。
·pg_stat_tmp:统计子系统的永久文件。
·pg_subtrans:子事务状态数据。
·pg_tblsp:存储了指向各个用户自建表空间实际目录的链接文件。
·pg_twophase:使用两阶段提交功能时分布式事务的存储目录。
·pg_wal:WAL日志的目录,在PostgreSQL 10版本之前此目录是“pg_xlog”。
·pg_xact:Commit Log的目录,在PostgreSQL 10版本之前此目录是“pg_clog”。

在默认表空间的base目录下有很多子目录,这些子目录的名称与相应数据库的OID相同。

osdba=# select oid, datname from pg_database;
oid | datname
-------+-----------
12937 | postgres
16384 | osdba
1 | template1
12936 | template0
(4 rows)
osdba=# \q
osdba-mac:~ osdba$ ls -l $PGDATA/base
total 0
drwx------ 304 osdba staff 9728 Jan 27 17:22 1
drwx------ 304 osdba staff 9728 Jan 27 17:22 12936
drwx------ 304 osdba staff 9728 Feb 12 09:59 12937
drwx------ 329 osdba staff 10528 Feb 12 22:09 16384
drwx------ 2 osdba staff 64 Feb 5 23:00
pgsql_tmp

例如上面的内容中“osdba”数据库的oid为16384,则它对应的子目录名称就是16384。在16384目录下,存放着“osdba”这个数据库的表、索引等数据文件。每个表或索引都会分配一个文件号relfilenode,数据文件格式则以“[.顺序号]”命名,每个文件最大为1GB,当表或索引的内容大于1GB时,就会从1开始生成
顺序号。所以一张表的数据文件的路径为:

<默认表空间的目录>/<database oid> /<relfilenode>[.顺序号]

而一张表或索引的“relfilenode”是记录在系统表pg_class的relfilenode字段中的。如果要查询数据库“osdba”中表“test01”的数据文件,根据前面已经查出的数据库“osdba”的oid(为16384)来查,假设表“test01”是在默认表空间下的,那么查询这张表的relfilenode的命令如下:

select relnamespace, relname, relfilenode from pg_class where relname='test01';

可以看出这个表的relfilenode为“33103”,则这张表的数据文件为“$PGDATA/base/16384/33103”:

PostgreSQL 的物理存储结构主要指数据库在磁盘上的文件组织方式,其核心围绕 $PGDATA 目录展开,包含以下关键组件:

1. 数据目录($PGDATA)

  • base/:存储所有数据库的表和索引数据,每个子目录以数据库 OID 命名(如 base/12345/)。
  • global/:存储全局系统表(如 pg_database)和共享目录。
  • pg_wal/:Write-Ahead Logging(WAL)文件,确保事务持久性和崩溃恢复。
  • pg_xact/:事务提交日志(原 pg_clog),记录事务状态。
  • pg_tblspc/:表空间的符号链接,指向用户定义的存储路径。
  • pg_stat/ 和 pg_stat_tmp/:统计信息文件。
  • pg_log/:运行日志(需手动开启)。

2. 表和索引文件

  • 每个表/索引对应一个或多个文件,文件名为其 relfilenode(如 12345 或 12345.1)。
  • 超过 1GB 的文件会自动分裂为多个段(如 12345.1, 12345.2)。

3. 数据页结构

  • 页大小:默认 8KB(编译时可调),是 I/O 的基本单位。
  • 页组成:
    • 页头(PageHeaderData):存储 LSN、空闲空间指针等元数据。
    • 元组(Tuple):行数据,包含头部(事务 ID、长度)和实际列值。
    • 空闲空间映射(FSM):通过 _fsm 文件跟踪页内可用空间,采用树形结构加速插入定位。

4. TOAST 机制

  • 大字段(如文本、字节数组)超过 2KB 时,自动触发 TOAST 技术,将数据拆分到独立的 TOAST 表中,原表仅保留指针。

5. 表空间(Tablespace)

  • 逻辑存储单元,允许将表/索引分布到不同磁盘路径:
CREATE TABLESPACE fast_disk LOCATION '/ssd/pg_data';

示例结构

$PGDATA/
├── base/
│   └── 12345/          # 数据库 OID 目录
│       ├── 56789       # 表文件(relfilenode)
│       └── 56789_fsm   # 空闲空间映射文件
├── pg_wal/
│   └── 000000010000000000000001  # WAL 段文件
└── pg_tblspc/
    └── 16384 -> /ssd/pg_data  # 表空间链接

通过以上设计,PostgreSQL 实现了高并发、可扩展和崩溃安全的存储引擎。

表空间的目录

前面讲解了表的数据文件在默认表空间下是如何存储的。对于用户创建的表空间,相当于一个对应的目录,在创建完一个表空间后,会在表空间的根目录下生成带有“Catalog version”的子目录,示例如下:

CREATE TABLESPACE tbs01 LOCATION '/home/osdba/tbs01';

此时,在表空间的根目录下会生成一个子目录名“PG_12_201909212”:
子目录“PG_12_201909212”中的“12”代表大版本,而“201909212”就是“Catalog version”,“Catalog version”可以由pg_controldata命令查询出来:

pg_controldata |grep "Catalog version number"
Catalog version number: 201909212

在“PG_12_201909212”子目录下,又会有一些子目录,这些子目录的名称就是数据库的oid。

osdba-mac:~ osdba$ ls -l
/Users/osdba/tbs12_01/PG_12_201909212/
total 0
drwx------ 3 osdba staff 96 Feb 12 22:25 16384

比如,上面的“16384”子目录就是“osdba”数据库的oid。

所以对于用户创建的表空间,表和索引存储数据文件的目录名为:

<表空间的根目录>/< Catalog version 目录>/<database oid>/<relfilenode>[.顺序号]

归档文件

什么是“归档文件”(Archive Files)
在 PostgreSQL 语境里,“归档文件”通常指 被归档出去的 WAL 段文件。
当启用归档(archive_mode = on,并配置 archive_command)后,后台进程 archiver 会在每一次 WAL 段文件写满并被切换时,把该段文件拷贝到归档目录/远程存储,形成“归档文件”。

  • 1.作用
    • Point-in-Time Recovery(PITR):
    基础备份 + 归档文件可按时间线向前回放到任意时刻。
    • 增量备份/异地容灾:
    归档文件可同步到 standby 节点、对象存储、磁带库,实现持续增量备份。
    • 防止 WAL 被循环覆盖:
    如果没有归档,pg_wal 下的段文件在 checkpoint 后会被复写,数据无法追溯。

  • 2.典型配置示例

-- postgresql.conf
wal_level = replica        -- 或 logical
archive_mode = on
archive_command = 'rsync -a %p /archive/%f'
archive_timeout = 600      -- 可选:10 分钟强制切段
  • 3.与 WAL 段文件的区别
    • WAL 段文件:位于 pg_wal,正在使用或即将被复写。
    • 归档文件:位于 archive_command 指定的目录,只读,长期保留。

  • 4.与逻辑/物理备份文件的区别
    • pg_dump / pg_dumpall 产生的 .sql、.dump 是逻辑备份文件。
    • pg_basebackup 产生的 tar 包是物理基础备份。
    • 归档文件是增量 WAL,与基础备份组合成完整的持续备份方案。

小结

PostgreSQL 数据目录中既有配置、控制、数据、日志文件,也有 WAL 和临时文件;“归档文件”特指被归档出去的 WAL 段,是 PITR 与增量备份的核心,确保在发生介质故障或误操作时,能够把数据库恢复到任意时间点。

应用程序访问接口

在整个应用架构中,PostgreSQL数据库工作在操作系统与应用程序之间。PostgreSQL数据库可以运行在各种操作系统(如Linux、Windows、AIX、FreeBSD、Solaris)和其他类UNIX操作系统上。
不同的编程语言使用不同的驱动程序连接到PostgreSQL数据库上。总的来说这些驱动分为两类:

  • ·使用纯语言实现的PostgreSQL驱动,如JDBC、.NET等方式。这种连接方式不需要libpq库。
  • ·通过包装PostgreSQL的C语言接口库libpq实现的驱动,比如Python下的psycopg库、Perl的DBD::pg模块、ODBC等。所以在安装这些驱动之前,需要先安装PostgreSQL的libpq库。

在一般情况下,各种应用程序都是通过TCP/IP连接到PostgreSQL数据库的。如果应用程序与数据库在一台机器上,也可以使用UNIX domain sockets连接到PostgreSQL数据库。

不同编程语言的PostgreSQL驱动介绍

  • ·C/C++语言:C语言直接使用libpq库就可以访问PostgreSQL数据库。
  • ·Java:Java应用程序就可以通过JDBCAPI使用相同的方法访问不同类型的数据库,如MySQL、Oracle或PostgreSQL。
  • ·C#:在.NET中由Npgsql驱动来访问PostgreSQL。Npgsql完全由C#语言实现,不需要其他语言的依赖库。
  • ·ODBC:PosgreSQL数据库的ODBC驱动psqlODBC是建立在libpq之上的,所以需要libpq库的支持。
  • ·OLE-DB:第三方提供了PostgreSQL的OLE-DB驱动PgOleDb
  • ·Ruby:Ruby下连接PostgreSQL的Pg包是建立在libpq之上的。而Ruby也提供了不需要libpq库的纯语言驱动postgres-pr。
  • ·Perl:Perl语言制定了访问数据库的标准接口DBI模块,各种语言提供相应的DBD驱动即可。
  • ·PHP:在PHP中有相应的函数访问PostgreSQL,这些函数是建立在libpq之上的。
  • ·Golang:Google公司开发的Go语言。Go语言访问PostgreSQL可以使用https://github.com/lib/pq驱动、
  • ·Node.js:Node.js语言使用node-postgres驱动访问PostgreSQL。node-postgres提供了纯JavaScript驱动,也提供了绑定libpq的方式来访问PostgreSQL数据库。
posted @ 2026-05-12 16:18  数据库小白(专注)  阅读(51)  评论(0)    收藏  举报