数据库 Page 与 Buffer Pool
解释关系型数据库为什么按 Page 组织磁盘 I/O、Buffer Pool 如何缓存页面,以及这套存储模型为什么直接影响索引结构和查询性能。
[!info] related notes
- 所属 MOC:数据库 MOC
- 后续:B+Tree 数据库索引、数据库索引
- 查询:SQL 查询执行流程、索引选择性、基数与代价
- 持久化:WAL、Redo Log
数据库 Page 与 Buffer Pool
[!abstract] 一句话定义 关系型数据库通常以 Page(页)作为主要磁盘 I/O 与缓存单位,并由 Buffer Pool / Buffer Cache 管理内存中的 Page。查询性能常取决于需要访问多少 Page、访问模式是否连续,以及这些 Page 是否已在内存中。
为什么不是“按行读磁盘”
存储设备和文件系统适合按块传输数据;如果每读取一行都单独做随机 I/O,成本会非常高。
Disk / SSD
→ read fixed-size Page
→ Buffer Pool
→ executor accesses tuple / index entry
不同数据库页大小和布局不同,但“批量搬运、内存复用、减少随机 I/O”的目标相同。
一个 Page 里有什么
Page 通常包含:
- Page Header;
- Slot / Item Directory;
- Row / Tuple 或 Index Entry;
- Free Space;
- Page-level Metadata;
- 校验、日志位置或事务相关信息。
因此 Page 不是简单的“若干行拼在一起”,数据库还要维护空闲空间、可见性和更新位置。
Buffer Pool 做什么
读取 Page 时:
lookup page id in buffer pool
├─ hit → pin / latch → read memory
└─ miss → choose victim → flush if dirty → read from disk
核心职责包括:
- Page 查找与缓存;
- Pin / Reference,避免使用中被淘汰;
- Replacement Policy;
- Dirty Page 管理;
- Prefetch / Read-ahead;
- 与 WAL / Checkpoint 协作。
Buffer Hit 不等于 Query 一定快
即使全部命中内存,仍可能因为扫描 Page 太多、锁竞争、CPU 计算、排序、Join 或内存复制而慢。
Buffer Miss 不等于每次都访问物理磁盘
操作系统 Page Cache、存储设备缓存和数据库自己的 Buffer Pool 可能共同参与。具体数据库是否绕过 OS Cache、如何 Flush,要按实现分析。
Dirty Page 与 WAL
修改行时,数据库通常先更新内存中的 Page,将其标记 Dirty,稍后再写回数据文件。
transaction changes page
→ WAL / Redo record becomes durable first
→ dirty page may flush later
这就是 Write-Ahead Logging 的核心约束:描述变更的日志必须先于对应 Data Page 持久化,崩溃后才能 Redo 未刷盘修改。
[!warning]
COMMIT不等于所有 Data Page 已写回 提交成功通常要求相关日志达到持久性条件;Dirty Page 可以由后台写回和 Checkpoint 稍后落盘。
随机 I/O 与顺序 I/O
- Random I/O:跳到不同位置读取少量 Page;
- Sequential Scan:连续读取大量 Page,更容易利用预读和存储吞吐。
因此命中表中大部分行时:
index scan + many random heap/table fetches
可能比顺序扫描全表更贵。优化器会根据选择性、相关性、缓存和成本参数做决策。
与 B+Tree 的关系
B+Tree 的 Node 通常映射到一个数据库 Page 或少量 Page:
fixed-size Page
→ internal node stores many keys + child pointers
→ high fan-out
→ low tree height
→ few Page accesses from root to leaf
所以数据库选择 B+Tree 的核心不是抽象复杂度 O(log n) 本身,而是降低外存 Page I/O。
Page Split、Fragmentation 与 Vacuum
Insert / Update 可能让 Page 空间不足:
- B+Tree 发生 Page Split;
- Heap / Table Page 留下 Dead Tuple 或 Fragment;
- 数据库需要 Vacuum、Compaction、Rebuild 或后台清理。
这些行为会影响写放大、缓存命中、树高和磁盘占用。
Buffer Pool 与连接池不是一回事
Buffer Pool → 缓存数据/索引 Page
Connection Pool → 复用数据库 Session / Connection
连接池满和 Buffer Cache Miss 都可能造成延迟,但排查路径完全不同。
观测与实验
可观察:
- Buffer Hit Ratio;
- Physical / Logical Reads;
- Dirty / Checkpoint Write;
- Page Read Latency;
- Sequential vs Random Scan;
- Table / Index Bloat;
- Cache Eviction 与 Working Set。
[!example] 最小实验 构造一张较大表,分别执行高选择性索引查询和返回大部分行的查询,用
EXPLAIN ANALYZE与 I/O 统计比较 Page 访问、回表和顺序扫描差异。
掌握标准
- 能解释 Page 为什么是数据库 I/O 单位。
- 能描述 Buffer Hit、Miss、Victim 和 Dirty Page。
- 能说明 WAL 为什么要先于 Data Page 持久化。
- 能解释为什么大范围查询可能放弃索引。
- 能区分 Buffer Pool、OS Page Cache 和 Connection Pool。