PostgreSQL 内部架构深度剖析 – wiki基地


PostgreSQL 内部架构深度剖析

PostgreSQL,通常简称为 Postgres,作为世界上最先进的开源关系数据库之一,以其稳定性、功能丰富性、强大的可扩展性以及对 SQL 标准的高度遵循而闻名。它被广泛应用于各种规模的应用场景,从小型项目到大型企业级数据仓库和高并发在线事务处理(OLTP)系统。要充分发挥 PostgreSQL 的潜力并有效地进行性能调优、故障排查和系统设计,深入理解其内部架构至关重要。本文将对 PostgreSQL 的核心架构组件进行详细剖析。

一、 整体架构概览

PostgreSQL 采用经典的客户端/服务器(Client/Server)模型。客户端应用程序通过网络连接到 PostgreSQL 服务器进程。服务器端的核心是一个多进程架构,主要由一个主控进程(Postmaster)和多个服务进程(Backend Processes / Postgres)以及一系列辅助后台进程(Background Processes)组成。

其架构可以大致分为以下几个层面:

  1. 进程架构 (Process Architecture):管理数据库服务的生命周期、连接处理和后台任务。
  2. 内存架构 (Memory Architecture):管理数据缓存、事务日志、锁信息等共享资源以及每个连接的私有工作区。
  3. 查询处理 (Query Processing):解析、分析、重写、优化和执行 SQL 查询。
  4. 存储架构 (Storage Architecture):管理数据在磁盘上的物理和逻辑组织。
  5. 事务与并发控制 (Transaction and Concurrency Control):确保 ACID 特性,并通过 MVCC 机制处理并发访问。
  6. 预写式日志与恢复 (Write-Ahead Logging and Recovery):保证数据持久性和系统崩溃后的恢复能力。

下面我们将逐一深入探讨这些核心部分。

二、 进程架构 (Process Architecture)

PostgreSQL 服务器启动时,首先运行的是 postmaster 进程。

  1. Postmaster (主控进程)

    • 职责:作为所有数据库活动的父进程,负责服务器的启动和关闭。它监听来自客户端的连接请求。
    • 连接处理:当接收到新的客户端连接请求时,postmaster 会进行身份验证。验证通过后,它会 fork(或在 Windows 上创建)一个新的服务进程postgres 进程)来专门处理该客户端的所有后续请求。
    • 管理后台进程postmaster 还负责启动和监控各种必要的后台辅助进程
    • 共享内存管理:在服务器启动时,postmaster 负责分配和初始化主要的共享内存区域。
    • 关闭管理:接收关闭信号,并协调所有子进程安全退出。
  2. Backend Processes (服务进程 / postgres 进程)

    • 一对一服务:每个客户端连接都由一个独立的 postgres 进程处理。这种设计提供了良好的进程隔离性,一个后端进程的崩溃通常不会影响其他连接。
    • 查询执行:这是实际执行客户端 SQL 查询、处理事务和返回结果的进程。它会与共享内存、存储系统等进行交互。
    • 资源隔离:每个后端进程拥有自己的私有内存区域,用于查询执行期间的排序、哈希等操作。
  3. Background Processes (后台辅助进程)
    这些进程与 postmaster 一同启动,并在后台持续运行,执行各种数据库维护和管理任务,不直接处理客户端请求。关键的后台进程包括:

    • Checkpointer (检查点进程):定期将共享内存中已修改的数据缓冲区(“脏页”)写入磁盘。这是保证数据持久性和限制崩溃恢复时间的关键机制。检查点触发条件包括时间间隔 (checkpoint_timeout) 和 WAL 日志量 (max_wal_size)。
    • Background Writer (后台写进程):辅助 checkpointer,以较低频率、持续地将脏页写入磁盘。目的是减轻 checkpointer 在高峰期的 I/O 压力,并为后端进程腾出干净的缓冲区,避免它们因找不到可用缓冲区而自己执行写盘操作。
    • WAL Writer (WAL 写进程):负责将内存中的 WAL(预写式日志)缓冲区的内容刷新到持久化存储(磁盘上的 WAL 文件)。这确保了事务日志先于数据页写入磁盘,是 WAL 机制的核心。
    • Autovacuum Launcher & Workers (自动清理启动器与工作进程):PostgreSQL 使用多版本并发控制(MVCC),会产生不再对任何事务可见的“死亡元组”(Dead Tuples)。Autovacuum 负责定期扫描表,回收这些死亡元组占用的空间,并更新表的统计信息(供查询优化器使用)以及防止事务 ID 回卷(Transaction ID Wraparound)。Launcher 负责调度,Workers 执行实际清理任务。
    • Archiver (归档进程):当启用了 WAL 归档 (archive_mode = on) 时,此进程负责将已写满的 WAL 文件复制到指定的归档位置。这对于基于时间点的恢复(PITR)和流复制至关重要。
    • Stats Collector (统计信息收集进程):收集关于数据库活动的统计信息,如表和索引的访问情况、函数调用次数等。这些信息存储在系统表中,可用于性能监控和查询优化。
    • Logger (日志进程):如果配置了日志收集 (logging_collector = on),此进程负责将后端进程和后台进程产生的错误和日志消息写入日志文件。

三、 内存架构 (Memory Architecture)

PostgreSQL 的内存主要分为两大部分:共享内存和本地内存(每个后端进程私有)。

  1. Shared Memory (共享内存)
    这是服务器启动时由 postmaster 分配的一块大内存区域,所有后端进程和后台进程都可以访问。它包含了数据库运行所需的全局数据结构和缓存。主要组成部分有:

    • Shared Buffer Pool (共享缓冲区):这是 PostgreSQL 内存管理的核心,也是最重要的性能调优参数 (shared_buffers)。它缓存了从磁盘读取的数据页(表和索引)。当后端进程需要访问某个数据页时,会首先在共享缓冲区查找。如果命中,则避免了昂贵的磁盘 I/O。写操作也是先修改缓冲区中的页,使其变“脏”,后续由 checkpointerbgwriter 写回磁盘。
    • WAL Buffers (WAL 缓冲区):用于缓存事务日志记录(WAL records)。事务提交时,相应的 WAL 记录必须先写入 WAL 缓冲区,并由 WAL Writer 刷新到磁盘上的 WAL 文件,然后事务才算真正持久化。大小由 wal_buffers 参数控制。
    • Commit Log (CLOG) Buffers (提交日志缓冲区):用于缓存事务的状态(进行中、已提交、已中止)。MVCC 判断元组可见性时需要快速查询事务状态,CLOG 缓冲区加速了这一过程。
    • Lock Tables (锁表):管理各种类型的锁(表锁、行锁、页锁、对象锁等)的信息,协调并发事务对共享资源的访问。
    • Process/Session Information: 存储当前活动进程和会话的信息。
    • Other Caches and Structures: 如用于管理 Subtransaction (子事务) 的 Subtrans Buffer, 多 Xact 相关缓存, 共享的统计信息计数器等。
  2. Local Memory (本地内存 / Per-Backend Memory)
    每个后端进程(postgres 进程)在处理客户端请求时,会分配自己私有的内存区域,用于执行特定的操作。这部分内存的大小和使用是动态的。主要组成部分包括:

    • Work Memory (work_mem): 用于查询执行中的内部排序(如 ORDER BY, DISTINCT)、哈希表(如 Hash Join, Hash Aggregate)和位图扫描(Bitmap Heap Scan)等操作。如果操作所需内存超过 work_mem,PostgreSQL 会转而使用磁盘上的临时文件,导致性能显著下降。合理设置 work_mem 对复杂查询性能至关重要。
    • Maintenance Work Memory (maintenance_work_mem): 用于数据库维护操作,如 VACUUM, CREATE INDEX, ALTER TABLE ADD FOREIGN KEY 等。这些操作通常需要较大的内存,将其与普通查询的 work_mem 分开设置可以更灵活地管理资源。
    • Temporary Buffers (temp_buffers): 用于缓存访问临时表的数据。

四、 查询处理 (Query Processing Pipeline)

当一个 SQL 查询到达后端进程时,它会经历一个标准的处理流程:

  1. Parser (解析器)

    • 词法分析与语法分析:检查查询字符串的语法是否符合 SQL 标准和 PostgreSQL 的扩展。
    • 生成解析树 (Parse Tree):将合法的 SQL 语句转换成一个内部的树状数据结构,表示查询的语法结构。
  2. Analyzer / Semantic Analyzer (分析器 / 语义分析器)

    • 语义检查:验证解析树中的对象(表、列、函数等)是否存在,权限是否足够,数据类型是否匹配等。
    • 生成查询树 (Query Tree):将解析树转换为更详细的查询树,包含了所有必要的语义信息和对象标识符(OID)。
  3. Rewrite System (重写系统)

    • 规则应用 (Rule System):如果数据库中定义了针对查询操作的规则(例如,用于实现视图或策略),重写系统会根据这些规则修改查询树。视图的展开就是在这里完成的。
  4. Planner / Optimizer (规划器 / 优化器)

    • 核心任务:为给定的查询树(可能已被重写)生成最优的执行计划(Execution Plan)。这是 PostgreSQL 最复杂的部分之一。
    • 生成候选计划:优化器会考虑多种可能的执行方式(例如,全表扫描 vs. 索引扫描,嵌套循环连接 vs. 哈希连接 vs. 合并连接),并生成多个候选的执行计划。
    • 成本估算 (Cost Estimation):基于收集到的表和索引的统计信息(由 ANALYZE 命令或 Autovacuum 收集),优化器为每个候选计划估算执行成本(主要考虑 CPU 和 I/O 成本)。
    • 选择最优计划:选择估算成本最低的那个执行计划。
  5. Executor (执行器)

    • 解释执行计划:接收优化器生成的执行计划树,并按照计划树的指令执行。
    • 数据访问:通过访问方法(Access Methods,如顺序扫描、索引扫描)与存储管理器交互,从表或索引中获取数据。
    • 操作执行:执行计划中的各种操作节点,如连接(Join)、排序(Sort)、聚合(Aggregate)、过滤(Filter)等。
    • 返回结果:将最终处理结果返回给客户端。

五、 存储架构 (Storage Architecture)

PostgreSQL 在磁盘上管理数据有其特定的结构:

  1. 逻辑结构

    • Tablespaces (表空间):允许将数据库对象(表、索引等)存储在不同的物理位置(文件系统目录)。这对于管理存储空间、优化 I/O 性能很有用。默认有 pg_defaultpg_global
    • Databases (数据库):一个 PostgreSQL 服务器实例(集群)可以管理多个独立的数据库。数据库之间是隔离的。
    • Schemas (模式):在一个数据库内部,对象(表、函数、类型等)被组织在模式中。模式提供了一个命名空间。默认模式是 public
  2. 物理结构

    • 数据目录 (Data Directory)PGDATA 环境变量指向的位置,包含了数据库集群的所有数据文件和配置文件。
    • 文件与目录:在 PGDATA 下,每个数据库对应一个子目录(以数据库 OID 命名)。每个表和索引通常存储为一个或多个独立的文件(文件名通常是它们的 OID 或 filenode)。还有 global 目录(存储集群范围的表)、pg_wal 目录(存储 WAL 文件)、pg_clog 目录(存储事务提交状态)等。
    • 数据页 (Pages / Blocks):表和索引的数据被组织成固定大小(通常是 8KB)的页(或块)。这是 PostgreSQL 进行 I/O 操作的基本单位。
    • 页结构:每个数据页包含页头(Page Header Data,含 LSN、校验和等)、行指针数组(Item Identifier Array / Line Pointers,指向元组)、实际的行数据(Tuples / Items)和特殊空间(Special Space,如 B-Tree 索引的高键)。
  3. 数据存储

    • Heap Tables (堆表):常规表数据存储的主要方式。行(元组)在数据页中无特定顺序存储。
    • Indexes (索引):为了加速数据检索,PostgreSQL 支持多种索引类型,如 B-Tree(默认,适用于范围查询和等值查询)、Hash(仅适用于等值查询)、GiST、SP-GiST、GIN(适用于全文搜索、数组等复杂类型)、BRIN(适用于物理存储有序的大表)。索引存储了键值和指向实际数据行(TID)的指针。
    • TOAST (The Oversized Attribute Storage Technique):当行中的某个字段值过大,无法直接放入数据页时,TOAST 机制会自动将其压缩或切分成小块存储在一个独立的 TOAST 表中,原表中只保留一个指针。

六、 事务与并发控制 (Transaction and Concurrency Control)

PostgreSQL 严格遵循 ACID(原子性、一致性、隔离性、持久性)原则。

  1. ACID 实现

    • 原子性 (Atomicity):通过 WAL 和事务状态管理(CLOG)确保事务要么全部完成,要么完全回滚。
    • 一致性 (Consistency):由应用程序和数据库约束(主键、外键、检查约束等)共同保证。事务的原子性和隔离性是基础。
    • 隔离性 (Isolation):主要通过多版本并发控制(MVCC)实现。
    • 持久性 (Durability):通过 WAL(预写式日志)机制保证。事务提交时,其产生的日志记录必须先写入持久存储,然后才能认为事务成功。
  2. MVCC (Multi-Version Concurrency Control)

    • 核心思想:读取操作不会阻塞写入操作,写入操作也不会阻塞读取操作。通过为每个数据行(元组)保存多个版本来实现。
    • 行版本:每个元组包含系统列 xmin(创建该版本的事务 ID)和 xmax(删除或更新该版本的事务 ID)。ctid 指向行在表中的物理位置,更新操作实际上是插入一个新版本并将旧版本的 xmax 设为当前事务 ID。删除操作只是将行的 xmax 设为当前事务 ID。
    • 快照 (Snapshot):每个事务开始时会获取一个事务快照,定义了哪些事务 ID 对它来说是可见的(已提交且早于快照开始时间)或不可见的(未提交、已中止或晚于快照开始时间)。查询时,只返回对其快照可见的行版本。
    • 可见性判断:根据元组的 xmin, xmax 和当前事务的快照来判断一个元组版本是否对当前事务可见。
    • 死元组清理:MVCC 会留下不再对任何活动事务可见的“死元组”。VACUUM 进程(手动或自动)负责回收这些空间,并执行其他维护任务。
  3. Locking (锁)
    虽然 MVCC 极大地减少了锁的使用,但在某些情况下仍然需要显式锁:

    • 写操作:更新或删除行时,需要获取行级别的写锁(Row-level Lock),防止并发修改同一行。
    • DDL 操作:修改表结构等操作通常需要更高级别的锁(如表级别的 ACCESS EXCLUSIVE 锁),以防止并发访问。
    • 显式锁定SELECT ... FOR UPDATE/SHARE 等语句允许用户显式获取行锁。
      PostgreSQL 提供了多种锁粒度(表、页、行、事务 ID、对象等)和锁模式(共享锁、排他锁等),并有死锁检测机制。

七、 预写式日志与恢复 (Write-Ahead Logging and Recovery)

  1. WAL (Write-Ahead Logging)

    • 原理:任何对数据文件的修改(包括表和索引),都必须先将描述这些修改的日志记录(WAL record)写入到持久化的 WAL 文件中。只有当相关的 WAL 记录被安全写入磁盘后,数据页的修改才能刷新到磁盘。
    • 目的:保证持久性(Durability)。即使在数据页尚未写回磁盘时发生系统崩溃,也可以通过重放 WAL 日志来恢复数据库到一致的状态。
    • 性能优势:WAL 写入是顺序 I/O,通常比随机写入数据页快得多。这允许延迟和批量写入数据页,提高性能。
  2. Checkpointing (检查点)

    • 目的:限制崩溃恢复所需重放的 WAL 日志量。检查点确保在该点之前所有已提交事务产生的脏页都已被写入磁盘(或至少 WAL 记录已确保能恢复它们)。
    • 过程:触发检查点时,Checkpointer 进程会将当前共享缓冲区中所有脏页的信息记录到一个检查点记录(Checkpoint Record)中,并确保相关的 WAL 记录已刷盘,然后努力将这些脏页写回磁盘。
  3. Recovery (恢复)

    • 过程:当数据库因崩溃重启时,它会从最后一个成功的检查点记录开始,顺序读取并重放 WAL 文件中的日志记录,将数据库状态恢复到崩溃前的最后一致状态。所有未完成的事务都会被回滚。

八、 可扩展性 (Extensibility)

PostgreSQL 设计之初就考虑了高度的可扩展性,允许用户自定义和扩展数据库功能:

  • 自定义数据类型、函数、操作符、聚合函数
  • 扩展 (Extensions):打包的功能模块,可以方便地添加到数据库中(如 PostGIS 地理空间处理、pgvector 向量数据库功能等)。
  • 外部数据包装器 (Foreign Data Wrappers, FDW):允许在 PostgreSQL 中像访问本地表一样访问存储在其他数据库或外部系统中的数据。
  • 过程语言:支持多种过程语言(PL/pgSQL, PL/Python, PL/Perl, PL/Java 等)编写存储过程和函数。

九、 总结

PostgreSQL 的内部架构是一个精心设计、历经多年发展和实战检验的复杂系统。其多进程模型、分层的内存管理、成熟的查询处理流程、基于 MVCC 的高并发控制、可靠的 WAL 和恢复机制,以及强大的可扩展性,共同构成了 PostgreSQL 强大功能和卓越性能的基础。深入理解这些内部工作原理,对于数据库管理员(DBA)、开发人员和系统架构师来说,是有效使用、管理和优化 PostgreSQL 的关键。随着社区的持续活跃和技术的不断演进,PostgreSQL 的内部架构也在不断优化和增强,继续保持其在数据库领域的重要地位。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部