页面布局的设计挑战

数据页设计面临两个核心矛盾:

  1. 变长字段:字符串、JSON、数组长度不固定
  2. 原地更新 vs 追加写入:如何平衡写性能与读性能

Slotted Pages

Slotted Pages 是关系数据库最主流的页面布局,PostgreSQL 和 MySQL InnoDB 均采用此设计。

┌──────────────────────────────────────┐
│              Page Header              │
│  ┌──────────┬──────────────┐         │
│  │ LSN (8B) │ Free Space Ptr│ ...     │
│  └──────────┴──────────────┘         │
├──────────────────────────────────────┤
│                                      │
│          Free Space (Gap)            │
│           ↓ ↓ ↓ ↓ ↓ ↓ ↓             │
│                                      │
├──────────────────────────────────────┤
│  Tuple 2 (id=2, name="Bob")         │
├──────────────────────────────────────┤
│  Tuple 1 (id=1, name="Alice")       │
├──────────────────────────────────────┤
│              Item Pointers            │
│  ┌─────────────────────────────────┐ │
│  │ Slot 1: offset=XXX, length=YYY  │ │
│  │ Slot 2: offset=XXX, length=YYY  │ │
│  └─────────────────────────────────┘ │
└──────────────────────────────────────┘

核心特性

  1. Tuple 从页尾向前增长,Slot 数组从页头向后增长
  2. Slot 数组记录每条 Tuple 的偏移和长度,支持变长字段
  3. 删除只需标记 Slot,无需移动数据(通过 vacuum 回收空间)
  4. Heap-Only Tuple (HOT) 更新:在同页内创建新版本,不更新索引

PostgreSQL 的具体实现

// src/include/storage/bufpage.h

typedef struct PageHeaderData {
    PageXLogRecPtr pd_lsn;      // WAL LSN
    uint16         pd_checksum; // 校验和
    uint16         pd_flags;    // 标志位
    LocationIndex  pd_lower;    // 空闲空间的起始偏移
    LocationIndex  pd_upper;    // 空闲空间的结束偏移
    LocationIndex  pd_special;  // 特殊空间偏移
    uint16         pd_pagesize_version;
    ShortTransactionId pd_prune_xid;
    ItemIdData     pd_linp[FLEXIBLE_ARRAY_MEMBER]; // 行指针数组
} PageHeaderData;

其他布局方案

日志结构页面

不覆盖旧数据,追加写入新版本:

Page v1: [Tuple A, Tuple B]
Page v2: [Tuple A, Tuple B', Tuple C]  ← 追加 B' 和 C
Page v3: [Tuple A, Tuple B', Tuple C']
  • 优势:写入极快(顺序追加),崩溃恢复简单
  • 劣势:需要 compaction 合并版本,读放大
  • 代表:LSM-Tree(RocksDB)、WiredTiger

PAX(Partition Attributes Across)

页面内按列分组:

┌─────┬──────────┬──────────┬──────────┐
│     │ Column A │ Column B │ Column C │
│Row 1│  val_a1  │  val_b1  │  val_c1  │
│Row 2│  val_a2  │  val_b2  │  val_c2  │
│ ... │   ...    │   ...    │   ...    │
└─────┴──────────┴──────────┴──────────┘
  • 优势:列式计算高效,压缩比高
  • 劣势:行级操作需要多次内存跳转
  • 代表:DuckDB、MonetDB/X100

总结

方案 适用场景 代表系统
Slotted Pages OLTP(读写均衡) PostgreSQL, MySQL
日志结构 写密集 RocksDB, Cassandra
PAX/列存 分析查询 DuckDB, Vertica

参考