2026年01月19日/ 浏览 11
在数据库系统中,存储管理是决定性能、可用性与扩展性的核心模块。PostgreSQL(以下简称PG)作为开源关系型数据库的标杆,其存储管理机制融合了传统数据库的成熟设计与高效优化。
数据库存储系统遵循"金字塔"层级结构,不同层级的访问延迟差异显著,直接决定了存储引擎的设计思路。从底层到顶层,存储层级及典型访问延迟如下:
存储层级
访问延迟(典型值)
技术载体
寄存器
0.5ns
CPU内部寄存器
L1缓存
0.5ns
CPU高速缓存
L2缓存
7ns
CPU二级缓存
主内存
100ns
DRAM
闪存盘
150,000ns(150μs)
SSD/NVM
传统硬盘
10,000,000ns(10ms)
HDD
网络存储
~30,000,000ns(30ms)
分布式存储集群
磁带归档
1,000,000,000ns(1s)
离线归档设备
这种层级差异催生了"局部性原理"在存储引擎设计中的核心应用:通过将热点数据缓存至高层级存储(内存、缓存),减少低层级存储(磁盘、网络)的访问次数,从而降低整体延迟。
页面(Page)是PG存储的最小IO单位(默认8KB),元组(Tuple)是数据记录的存储载体,二者的结构设计直接影响存储效率与访问性能。
PG页面采用固定8KB大小(可配置),结构分为四部分,严格遵循"头信息-指针-数据-特殊区域"的组织逻辑:
typedef struct PageHeaderData { uint32 pd_lsn; // 页面最后修改的LSN(日志序列号) uint16 pd_checksum; // 页面校验和 uint16 pd_flags; // 页面状态标志(如脏页、空闲) uint16 pd_lower; // 空闲空间起始偏移 uint16 pd_upper; // 空闲空间结束偏移 uint16 pd_special; // 特殊区域起始偏移 uint16 pd_pagesize_version; // 页面大小与版本 uint32 pd_prune_xid; // 可清理的最小事务ID } PageHeaderData;• Page Header:8字节固定长度,存储页面元信息(LSN、校验和、空闲空间边界等),保障页面完整性与可追溯性。• Line Pointers(项指针):每个指针指向页面内的元组,记录元组的偏移量与长度,形成"指针数组",支持快速定位元组。• Heap Tuples(元组数据):存储实际数据记录,元组间通过空闲空间(Hole)分隔,支持动态插入与删除。• Special Area:存储索引相关信息(如B-Tree的分支指针),位置固定在页面尾部,长度可变。元组是数据记录的物理载体,其结构分为Header与Value两部分,Header包含事务与状态信息,Value存储字段数据:
核心字段及作用:
• xmin:元组创建时的事务ID,用于MVCC可见性判断。• xmax:元组删除/更新时的事务ID,更新操作本质是标记xmax并创建新元组。• ctid:元组标识符(页面号+项指针索引),用于定位元组在页面中的位置。• infomask:元组状态标志(如是否包含NULL值、是否被锁定、MVCC可见性状态)。• bits:NULL值位图,标记哪些字段为NULL,节省存储空间。• OID(可选):用于唯一标识元组,适用于系统表等需要全局唯一标识的场景。Buffer管理的核心目标是通过内存缓存磁盘页面,减少磁盘IO次数,PG采用"三级结构+状态管理"的Buffer机制,实现高效的页面缓存与访问控制。
PG的Buffer池通过三层结构实现页面的快速查找、状态管理与数据存储:
层级
核心作用
数据结构
Buffer Table Layer
页面哈希查找,通过BufferTag快速定位缓存页面
哈希表(BufferLookupEnt)
Buffer Descriptors Layer
维护每个Buffer的状态信息(如脏页、引用计数)
BufferDesc结构体
Buffer Pool Layer
实际存储页面数据,按页面大小分配连续内存
连续内存块(默认8KB/块)
BufferDesc是Buffer管理的核心结构体,负责维护Buffer的状态与锁信息,其设计在PG94与PG11有显著优化:
应用读取数据时,Buffer管理模块的核心执行流程如下:
1. 判断是否扩展页面:若请求的块号为P_NEW,调用smgrnblocks扩展新页面,分配页面编号。2. 查找Buffer缓存:通过BufTableLookup查询哈希表,判断页面是否已在Buffer池中:• 命中:调用GetBufferDescriptor获取Buffer描述符,执行PinBuffer(增加引用计数),等待IO完成后返回Buffer。• 未命中:进入淘汰流程,获取空闲Buffer。3. 淘汰算法获取空闲Buffer:调用StrategyGetBuffer(默认Clock Sweep算法),筛选可淘汰页面:• 若页面为脏页(BM_DIRTY),先调用FlushBuffer刷盘,确保数据一致性。• 刷盘完成后,移除哈希表中的旧页面记录,插入新页面的BufferTag与描述符映射。4. 加载页面数据:通过smgrread接口从磁盘读取页面数据至Buffer Pool,返回Buffer描述符。当Buffer池无空闲页面时,需通过淘汰算法回收低效页面,PG的淘汰策略兼顾性能与实现复杂度,默认采用改进版Clock Sweep算法,同时支持多种经典算法适配不同场景。
算法
核心逻辑
优点
缺点
FIFO(先进先出)
按页面进入缓存的顺序淘汰,维护FIFO队列
实现简单,开销低
未考虑页面访问频率,可能淘汰高频访问的"老页面"(Belady异常)
LRU(最近最少使用)
淘汰最长时间未被访问的页面,维护访问时间戳
贴合局部性原理,命中率较高
维护时间戳开销大,对突发访问(如批量扫描)不友好
LFU(最不经常使用)
淘汰一定时期内访问次数最少的页面,维护访问计数器
考虑访问频率,适合热点稳定场景
计数器维护开销大,难以处理访问模式变化(如旧热点冷却)
2Q(双队列)
维护FIFO队列(候选集)与LRU队列(热点集),新页面先入FIFO,访问后移入LRU
平衡访问时间与频率,抗突发访问
队列管理复杂,内存开销略高
PG采用改进版Clock Sweep算法,基于引用计数(refcount)与使用计数(usagecount)实现高效淘汰,核心逻辑如下:
PostgreSQL的存储管理机制以"高效、可靠、可扩展"为核心设计目标,其核心设计理念可概括为三点:
1. 分层抽象:从存储层级(寄存器→磁盘)、数据单元(元组→页面→表空间)到管理模块(Buffer三层结构),通过分层抽象降低复杂度,提升可扩展性。2. 性能优先:通过Buffer缓存、页面紧凑存储、低开销淘汰算法,最小化磁盘IO;通过锁分离、原子操作优化,提升并发处理能力。3. 原生适配事务一致性:元组结构嵌入事务字段支持MVCC,脏页刷盘遵循WAL原则,保障数据可靠性与事务隔离性。