PostgreSQL 架构原理第二期:存储引擎深度解析 —— 堆表、元组结构与 TOAST 机制
Clyde Jin
04-24
DBA
38

PostgreSQL 架构原理第二期:存储引擎深度解析 —— 堆表、元组结构与 TOAST 机制

引言

在上一期中,我们从整体架构入手,探讨了 PostgreSQL 的进程模型、共享内存、查询处理全流程、WAL 以及缓冲区管理器。理解这些上层机制后,一个自然的问题是:数据最终是如何在磁盘上组织存储的?PostgreSQL 的存储引擎以“堆表 + 索引 + TOAST”的组合方式,实现了高效的按行存取、可变长度字段支持以及 MVCC 所需的多版本管理。

本文将深入数据文件的内部世界,详细讲解以下内容:

  1. 逻辑结构与物理文件的映射关系
  2. 堆表文件的组成:主文件、FSM、VM
  3. 页面(Page)的内部布局
  4. 元组(Tuple)的存储格式与关键字段
  5. TOAST 机制的触发条件与工作原理
  6. 索引的基本存储结构(以 B-tree 为例)

一、逻辑结构到物理文件的映射

PostgreSQL 中,一个数据库实例包含多个数据库,一个数据库包含多个模式,模式中包含表、索引等对象。每个表(或索引)在物理上对应着一个或多个磁盘文件。

1.1 OID 与 relfilenode

每个数据库对象都有一个唯一的对象标识符(Object Identifier, OID),存储在其系统表 pg_class 中。对于表和索引,pg_class.relfilenode 字段指定了磁盘上使用的文件名。绝大多数情况下 relfilenode = OID,但执行 TRUNCATEREINDEX 或某些 VACUUM FULL 操作时,relfilenode 会发生变化,从而创建一个新文件。

1.2 表空间与路径

表空间(tablespace)允许将不同的数据库对象存储在不同的文件系统位置上。每个表空间目录下包含以数据库 OID 命名的子目录,数据库子目录下存放着以 relfilenode 命名的文件。默认表空间 pg_default 使用数据目录下的 base 目录。

1.3 超过 1GB 的文件分段

为了避免某些操作系统对单个文件大小的限制(如 2GB),PostgreSQL 将大于 1GB 的文件自动分割成多个段(segment)。主文件名为 relfilenode,后续段命名为 relfilenode.1relfilenode.2,依此类推。段大小可在编译时通过 --with-segsize 修改,默认 1GB。


二、堆表的相关文件

对于一个普通的堆表(heap table),通常会伴随三个文件:

文件后缀 含义
主数据文件,存储实际的行数据
_fsm 空闲空间映射(Free Space Map)
_vm 可见性映射(Visibility Map)

2.1 空闲空间映射(FSM)

FSM 记录了每个数据页内部的空闲空间大小。当需要插入新行时,PostgreSQL 不需要扫描所有页面来寻找空闲空间,而是快速查询 FSM 找到足够空间的页面。FSM 本身使用一棵三层的树结构组织,存储在专用的 _fsm 文件中。

2.2 可见性映射(VM)

VM 标记哪些页面中全部元组对所有当前事务都是可见的(即没有任何需要清理的死元组)。VACUUM 操作可以跳过这些页面,显著提高清理效率。同时,仅索引扫描(Index-Only Scan) 依赖 VM 来判断是否可以直接使用索引元组而不回表访问堆元组。VM 以位图形式存储,每个位代表一个页面。


三、页面内部结构

PostgreSQL 磁盘数据的基本单位是 页面(Page),默认大小为 8KB。一个页面可以分为几个逻辑区域:

+---------------------------+  <-- 页头
| PageHeaderData (24 bytes)  |
+---------------------------+  <-- 行指针起始
| ItemIdData (4 bytes each)  |  指向实际元组的偏移量和长度
| ...                         |
+---------------------------+  <-- 空闲空间
| 未使用空间                  |
| ...                         |
+---------------------------+  <-- 元组数据起始
| 元组数据 (HeapTuple)        |  从页面尾部向前填充
| ...                         |
+---------------------------+  <-- 特殊空间 (可选)

3.1 页头 PageHeaderData

页头固定 24 字节(不含可选的特殊空间),包含以下重要字段:

  • pd_lsn:本页面最后一次修改对应的 WAL 日志的 LSN。
  • pd_checksum:页面的校验和(如果启用)。
  • pd_flags:标志位,如是否包含空行指针、是否全部可见等。
  • pd_lower:行指针区域末尾的偏移量(即空闲空间开始处)。
  • pd_upper:元组数据区域起始的偏移量(即空闲空间结束处)。
  • pd_special:特殊空间起始偏移量(例如 B-tree 索引页面用于存放右兄弟指针)。
  • pd_pagesize_version:页面大小及版本信息。
  • pd_prune_xid:本页面中需要清理的最早旧事务 ID,用于提示 VACUUM

行指针(ItemIdData,4 字节)包含元组在页面内的偏移量、长度以及一些标志位。行指针数组从 pd_lower增长,元组数据从 pd_upper增长。当两者相遇时,页面空间耗尽。

3.2 特殊空间

只有某些访问方法需要特殊空间。对于堆表,特殊空间大小为 0;对于 B-tree 索引页面,特殊空间存储了页面级别信息(如右兄弟页号)。


四、元组(Tuple)的存储格式

堆表中的每一行对应一个元组。元组包含两部分:元组头用户数据

4.1 元组头结构 HeapTupleHeaderData

元组头长度固定为 23 字节(通常对齐到 24 字节),关键字段如下:

字段 类型 含义
t_xmin TransactionId 插入该元组的事务 ID(XID)
t_xmax TransactionId 删除或更新该元组的事务 ID(0 表示未删除)
t_cid CommandId 在当前事务内插入/删除该元组的命令序号
t_ctid ItemPointerData 当前元组自身的位置(页面号 + 行指针索引),若被更新则指向新版本元组
t_infomask uint16 标志位,包含 XID 有效性、元组是否具有空属性、是否有变长字段、是否已提交/已中止等
t_infomask2 uint16 第二组标志位,以及当前元组所拥有的列数(hev_natts
t_hoff uint8 用户数据相对于元组起始位置的偏移量(即元组头的实际长度,可能包含位图和对齐填充)
t_bits[] 可选 空值位图,每个列对应 1 位

4.2 用户数据部分

紧接在元组头之后,存储各列的实际数值。根据列的数据类型和对齐要求,PostgreSQL 会进行适当填充,以保证整型、浮点型等能够自然对齐。

对于 变长类型(如 varchartextbytea),PostgreSQL 使用特殊的格式:

  • 长度 ≤ 127 字节时:使用 1 字节长度前缀,数据紧随其后。
  • 长度 > 127 字节时:使用 4 字节长度前缀(最高位置 1 表示扩展模式),数据单独存储。
  • 极长的数据(TOAST)会触发外部存储。

4.3 元组与 MVCC

PostgreSQL 采用 多版本并发控制(MVCC),即 UPDATE 操作不直接修改原有元组,而是插入一个新版本元组,并修改旧元组的 t_xmaxt_ctid 指向新版本。DELETE 操作仅设置 t_xmax。旧版本元组仍然留在页面中,直到被 VACUUM 清除。

元组头上的 t_infomask 中的 HEAP_XMIN_COMMITTED / HEAP_XMIN_ABORTED 等位用于快速判断元组的可见性,避免每次查询都去查找事务提交状态。


五、TOAST 机制:处理超大字段

5.1 触发条件

PostgreSQL 的页面大小固定为 8KB,但一个元组不能跨多个页面存储(B-tree 索引不允许跨页,堆表也不行)。因此,单个元组的总长度必须小于 8KB(实际稍小,因为页头和行指针占用空间)。当元组大小超过约 2KB 时,TOAST(The Oversized-Attribute Storage Technique)就会介入。

默认情况下,变长类型(textvarcharbyteajsonb 等)以及 复合类型 可以使用 TOAST。用户也可以通过 ALTER TABLE ... SET STORAGE 显式控制存储策略:

  • PLAIN:禁止 TOAST
  • EXTENDED:允许压缩和外部存储(默认)
  • EXTERNAL:允许外部存储但不压缩
  • MAIN:优先压缩,尽量不外部存储

5.2 工作原理

当插入或更新一行导致元组过大时,PostgreSQL 会:

  1. 压缩:如果策略允许压缩,对列数据使用 LZ 压缩算法(轻量级,快速解压)。
  2. 分块外部存储:如果压缩后仍然超过阈值(约 2KB),则将数据切分成多个 TOAST 块(默认一个块与页面大小相同,实际存储时每个块占一个页面),每个块作为一个独立元组存入一个专门的 TOAST 表 中。

TOAST 表与主表关联:主表为 mytable 时,其 TOAST 表名为 pg_toast.pg<em>toast</em><主表 OID>。TOAST 表自己也有 _fsm_vm 文件。

主表元组中的 TOAST 列会被替换为一个指针(varatt_external 结构),该指针包含:

  • TOAST 表的 OID
  • TOAST 块起始页号
  • 块长度等信息

查询时,如果只选择非 TOAST 列,PostgreSQL 可以完全跳过 TOAST 表;如果必须读取 TOAST 列,则按需从 TOAST 表中读取块,必要时自动解压。

5.3 TOAST 的优势

  • 避免跨页存储,保证堆表元组始终能容纳于一个页面内。
  • 节省 I/O:不需要读取整个大字段即可访问小字段。
  • 透明压缩:对应用层无感知,同时减少磁盘空间和 I/O 量。

六、索引存储概览(以 B-tree 为例)

虽然本系列未来会有专门章节介绍索引,这里先简要说明 B-tree 索引的存储特点,以与堆表对比。

6.1 B-tree 索引文件

B-tree 索引同样使用一个主文件(以及可能的 FSM 和 VM 文件),内部也划分为 8KB 页面。但与堆表不同,索引页面没有行指针数组,而是由一系列 索引元组(IndexTuple) 组成。每个索引元组包含:

  • 索引键值(一个或多个列的值)
  • 对应的堆元组的 TID(页号 + 行指针)

B-tree 页面结构分为 元数据页(通常第 0 页)、内部页叶子页。内部页存储指针和键值范围,叶子页存储索引元组并指向堆元组。

6.2 唯一索引与非唯一索引

  • 唯一索引:索引元组的键值在整个索引中唯一。
  • 非唯一索引:PostgreSQL 在内部将 TID 作为键值的后缀,使每个元组在逻辑上唯一,便于实现查重和并发控制。

6.3 与堆表的协作

当执行索引扫描时,PostgreSQL 首先在 B-tree 中查找到满足条件的索引元组,获得 TID 后再去堆表中抓取完整的堆元组(需要检查可见性)。如果 VM 标记了对应堆页面全部可见,则可以省略可见性检查,这也是 仅索引扫描 的前提。


总结与预告

本期我们深入探讨了 PostgreSQL 的存储层,梳理了从逻辑对象到物理文件的映射、堆表关联的三种文件(主文件、FSM、VM),并详细分析了页面和元组的内部布局,尤其是 MVCC 相关的头字段。然后介绍了 TOAST 机制如何优雅地处理超大字段,最后简述了 B-tree 索引的基本存储方式。

理解存储层有助于推断 I/O 行为、估算表和索引大小、诊断性能问题(例如高膨胀率可能是因为 VACUUM 不及时)。下一期我们将正式进入 事务与并发控制,系统讲解 PostgreSQL 的 MVCC 实现细节、事务快照、锁机制以及隔离级别,敬请期待。


思考题

  1. 对于频繁执行 UPDATE 的表,为什么 pg_class.relfilenode 可能不随 VACUUM FULL 改变?而 TRUNCATE 一定会改变?
  2. 一个元组被 UPDATE 十次且不执行 VACUUM,那么物理磁盘上最多会存在多少个该行的版本?
  3. TOAST 存储为什么优先压缩而不是直接外部存储?在哪种场景下应该使用 EXTERNAL 策略?

Star
Donate
PostgreSQL 架构原理第一期:从进程模型到核心内存机制
Previous
PostgreSQL 架构原理第三期:事务与并发控制 —— MVCC、快照与锁机制
Next

Leave a comment

Registration is not required

Clyde Jin
302 Articles
0 Comments
0 Like
Recent Posts

HiddenMerit Daily · Issue 59

📊 HiddenMerit Daily · Issue 59 Focus on Database Frontiers, Practical Insights for DBAs July 21, 2026 | 5 Selected Global Breaking News 01|Oracle Issues Urgent Warning: July 21 Release Update to Fix Large Number of High‑Risk Vulnerabilities, Immediate Deployment Recommended On July 13, Oracle issued an urgent security warning, strongly recommending that all customers running supported Oracle Database versions (including Oracle Database 19c and Oracle AI Database 26ai) immediately test and deploy the Release Update (RU) after its release on July 21. Background: New frontier AI models are significantly lowering the barrier to discovering and exploiting software vulnerabilities – these models can identify weaknesses, analyse software changes, reverse‑engineer security patches, and develop potential attack paths at unprecedented speed and scale. AI models are also becoming increasingly adept at combining multiple weaknesses across the application and data stack into complex attacks, even when individual weaknesses do not themselves pose a serious risk. As a result, protecting systems solely at the network or application layer is no longer sufficient; enterprises must protect the entire technology stack. Fixes Included in This RU: Oracle has collaborated with state‑of‑the‑art models from Anthropic and OpenAI to proactively identify and fix potential security vulnerabilities. The upcoming […]

HiddenMerit Daily · Issue 58

📊 HiddenMerit Daily · Issue 58 Focus on Database Frontiers, Practical Insights for DBAs July 20, 2026 | 5 Selected Global Breaking News 01|CAICT: Domestic Databases Enter Core System “Deep Water,” AI‑Native Leads New Industry Landscape On July 9, at the 2026 Trustworthy Database Development Conference, CAICT released the “Database Development Research Report (2026).” The report notes that domestic databases have basically completed peripheral system replacement and have officially entered the critical business system breakthrough phase. Key Data: The global database market reached $131.6 billion in 2025 (approximately RMB 894.09 billion). The Chinese database market reached $9.49 billion in 2025 (approximately RMB 64.48 billion), and is expected to reach RMB 97.974 billion by 2028, with a CAGR of 13.06%. The number of domestic database vendors has shrunk from a peak of 167 to 94, with a clear head‑concentration effect and an intensifying “Matthew effect.” AI‑Native Becomes the Main Theme: The report points out that database technology is accelerating its evolution toward the AI‑native direction, and the global database industry is entering a new phase of landscape restructuring. The role of databases is upgrading from “underlying support systems” to “core engines enabling intelligent decision‑making and business innovation.” AI agents are becoming […]

HiddenMerit Daily · Issue 56

📊 HiddenMerit Daily · Issue 56 Focus on Database Frontiers, Practical Insights for DBAs July 6, 2026 | 5 Selected Global Breaking News 01|Kingware Releases Manufacturing Scenario Database Evolution White Paper: SQL Server Replacement Enters “Deep Water” On July 5, CETC Kingware published a technical article titled “Database Evolution in Manufacturing Scenarios: How Kingware Replaces SQL Server,” pointing out that the data surge on industrial shop floors has exceeded the processing capacity of single‑node systems. Traditional database architectures that rely on vertical scaling are facing unprecedented performance challenges. Over the next 1‑3 years, manufacturing enterprises will no longer face only the “choice of replacement,” but must answer the strategic question: “How do we build an autonomous data foundation in the context of de‑IOE?” Three Paradigm Shifts: Hybrid Workloads Become the Norm: The IT architecture of modern factories is shifting from separated OLTP and OLAP to HTAP mode. The same data system must simultaneously handle tens of thousands of device instruction writes per second and minute‑level production report analysis. Distributed Architecture Becomes a Hard Requirement: When a single table exceeds 100 million rows with daily increments exceeding 1 million rows, the index maintenance cost of traditional single‑node databases rises exponentially. Autonomous […]

HiddenMerit Daily · Issue 57

📊 HiddenMerit Daily · Issue 57 Focus on Database Frontiers, Practical Insights for DBAs July 17, 2026 | 5 Selected Global Breaking News 01|CAICT Releases “Database Development Research Report (2026)”: China Market to Reach RMB 98 Billion by 2028, AI‑Native Becomes the Main Theme On July 9, at the 2026 Trustworthy Database Development Conference, the China Academy of Information and Communications Technology (CAICT) officially released the “Database Development Research Report (2026),” comprehensively revealing the latest landscape and evolution directions of the global and Chinese database markets. Key Data: Global Market: The global database market reached $131.6 billion in 2025 (approximately RMB 894.09 billion). Chinese Market: The Chinese database market reached $9.49 billion in 2025 (approximately RMB 64.48 billion), accounting for 7.2% of the global market. By 2028, the total Chinese database market is expected to reach RMB 97.974 billion, with a compound annual growth rate of 13.06% . Industry Landscape: There are 394 database product providers globally, with China and the US leading in vendor count. The number of global open‑source and closed‑source products is roughly balanced. Commercial databases account for over 70% in China, while the proportion of open‑source databases is increasing. After rapid growth from 2022 to 2024, […]
生成中...
扫描二维码
扫描二维码