PostgreSQL 18 从新手到大师:实战指南 - 5.3 存储引擎深入
一、PostgreSQL存储引擎概述
PostgreSQL存储引擎是数据库的核心组件之一,负责数据的存储、检索和管理。PostgreSQL采用了灵活的存储架构,支持多种存储方式和索引类型,能够适应不同的应用场景和工作负载。
1.1 存储引擎的作用
存储引擎的主要作用是:
- 管理数据的物理存储
- 提供高效的数据检索机制
- 支持事务处理和并发控制
- 实现数据完整性和一致性
- 支持备份和恢复功能
1.2 PostgreSQL存储架构
PostgreSQL存储架构主要包括以下几个层次:
- 数据库集群(Database Cluster):包含多个数据库
- 数据库(Database):包含多个模式
- 模式(Schema):包含多个数据库对象(表、索引、视图等)
- 表(Table):包含多个数据行
- 行(Row):包含多个列
- 数据块(Data Block):存储数据的基本单位,默认大小为8KB
二、堆表存储
PostgreSQL默认使用堆表(Heap Table)存储数据。堆表是一种无组织的存储结构,数据行按照插入顺序存储,不保证任何特定的顺序。
2.1 堆表结构
堆表由多个数据块组成,每个数据块包含以下部分:
- 数据块头(Block Header):包含块的基本信息,如块号、检查和、LSN等
- 行指针数组(Item Pointer Array):指向块中数据行的指针
- 空闲空间(Free Space):块中未使用的空间
- 数据行(Heap Tuples):实际存储的数据行
数据块结构示意图:
┌─────────────────┐
│ Block Header │
├─────────────────┤
│ Item Pointers │
├─────────────────┤
│ Free Space │
├─────────────────┤
│ Heap Tuples │
└─────────────────┘
2.2 数据行结构
每个数据行(Heap Tuple)包含以下部分:
-
行头(HeapTupleHeader):包含行的元数据,如:
- t_xmin:插入事务ID
- t_xmax:删除或更新事务ID
- t_cid:命令ID
- t_ctid:行的物理位置(块号:偏移量)
- t_infomask:标志位(如是否为空、是否有外部数据等)
- t_hoff:行头大小
-
用户数据(User Data):实际存储的列数据
2.3 MVCC实现
PostgreSQL使用多版本并发控制(MVCC)机制实现并发访问。MVCC的核心思想是:
- 每个事务看到的数据是一个一致的快照
- 修改数据时不直接覆盖旧数据,而是创建新的版本
- 旧版本的数据会被后台进程(VACUUM)清理
MVCC实现示例:
- 事务1插入一行数据,t_xmin=100,t_xmax=0
- 事务2更新该行数据,创建新的版本,t_xmin=200,t_xmax=0;旧版本的t_xmax=200
- 事务3读取该行数据,根据事务ID和隔离级别决定看到哪个版本
2.4 主要文件
src/backend/access/heap/heapam.c:堆表访问方法实现src/backend/access/heap/tuptoaster.c:大对象处理src/backend/storage/page/bufpage.c:数据块管理
三、索引存储
索引是提高查询性能的重要手段,PostgreSQL支持多种索引类型,每种索引类型适用于不同的查询场景。
3.1 B-Tree索引
B-Tree索引是PostgreSQL默认的索引类型,适用于等值查询、范围查询和排序操作。
3.1.1 B-Tree索引结构
B-Tree索引采用平衡树结构,每个节点包含多个键值对和指向子节点的指针。
B-Tree索引结构示意图:
┌───────────────┐
│ Root Page │
└───────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Branch Page │ │ Branch Page │ │ Branch Page │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
┌────────┴───────┐ ┌────────┴───────┐ ┌────────┴───────┐
│ Leaf Page 1 │ │ Leaf Page 2 │ │ Leaf Page 3 │
└───────────────┘ └───────────────┘ └───────────────┘
3.1.2 B-Tree索引插入流程
- 查找插入位置
- 如果叶子节点有足够空间,直接插入
- 如果叶子节点空间不足,分裂节点
- 向上传播分裂操作,必要时分裂父节点
- 如果根节点分裂,创建新的根节点
3.1.3 B-Tree索引查询流程
- 从根节点开始,比较键值,选择合适的子节点
- 递归向下遍历,直到到达叶子节点
- 在叶子节点中查找匹配的键值
- 返回指向堆表数据行的指针
3.2 其他索引类型
3.2.1 Hash索引
Hash索引适用于等值查询,使用哈希表实现。
特点:
- 只支持等值查询(=)
- 不支持范围查询(<, >, BETWEEN)
- 不支持排序
- 插入和查询速度快
3.2.2 GiST索引
GiST(Generalized Search Tree)是一种通用索引结构,支持多种数据类型和查询操作。
适用场景:
- 地理空间数据(PostGIS扩展)
- 全文搜索
- 数组和范围类型
- 自定义数据类型
3.2.3 SP-GiST索引
SP-GiST(Space-Partitioned GiST)是GiST的改进版本,适用于具有自然分区结构的数据。
适用场景:
- 点、线、面等几何数据
- 文本前缀搜索
- IP地址范围查询
3.2.4 GIN索引
GIN(Generalized Inverted Index)是一种倒排索引,适用于包含多个元素的数据类型。
适用场景:
- 数组类型
- JSONB和hstore类型
- 全文搜索
- 向量数据
3.2.5 BRIN索引
BRIN(Block Range Index)是一种块范围索引,适用于大型表和有序数据。
特点:
- 索引体积小
- 维护成本低
- 适用于有序数据(如时间序列)
- 查询性能不如B-Tree索引
3.3 索引管理
3.3.1 创建索引
-- 创建B-Tree索引
CREATE INDEX idx_users_email ON users(email);
-- 创建唯一索引
CREATE UNIQUE INDEX idx_users_username ON users(username);
-- 创建复合索引
CREATE INDEX idx_users_name_email ON users(name, email);
-- 创建GIN索引
CREATE INDEX idx_users_tags ON users USING GIN(tags);
3.3.2 查看索引
-- 查看表的索引
\d+ users
-- 查看索引大小
SELECT pg_size_pretty(pg_indexes_size('users'));
-- 查看所有索引
SELECT * FROM pg_indexes WHERE tablename = 'users';
3.3.3 重建索引
-- 重建单个索引
REINDEX INDEX idx_users_email;
-- 重建表的所有索引
REINDEX TABLE users;
-- 重建数据库的所有索引
REINDEX DATABASE mydb;
3.4 主要文件
src/backend/access/nbtree/:B-Tree索引实现src/backend/access/hash/:Hash索引实现src/backend/access/gist/:GiST索引实现src/backend/access/spgist/:SP-GiST索引实现src/backend/access/gin/:GIN索引实现src/backend/access/brin/:BRIN索引实现
四、WAL机制
WAL(Write-Ahead Logging)是PostgreSQL保证数据一致性和可靠性的核心机制。WAL确保在修改数据之前,先将修改操作记录到WAL日志中。
4.1 WAL的作用
WAL的主要作用是:
- 确保数据一致性:所有修改操作先写入WAL日志,再写入数据文件
- 支持崩溃恢复:数据库崩溃后,可以通过WAL日志恢复数据
- 支持Point-in-Time Recovery(PITR):可以恢复到任意时间点
- 支持复制:通过WAL日志实现主从复制
4.2 WAL文件结构
WAL日志存储在pg_wal目录下,由多个WAL段文件组成。每个WAL段文件默认大小为16MB(可以通过--with-wal-segsize编译选项修改)。
WAL段文件命名格式:000000010000000000000001,其中:
- 前8位:时间线ID
- 中间16位:逻辑日志文件号
- 最后8位:段号
4.3 WAL记录格式
每个WAL记录包含以下部分:
- 记录头:包含记录长度、记录类型、XID等信息
- 记录体:包含具体的修改操作,如插入、更新、删除等
- 记录尾:包含检查和等信息
4.4 WAL写入流程
- 数据库进程生成WAL记录
- 将WAL记录写入WAL缓冲区
- WAL写入进程(walwriter)定期将WAL缓冲区的内容写入WAL文件
- 对于关键操作(如事务提交),会立即调用
pgwalflush()将WAL缓冲区写入磁盘 - 当WAL文件满时,会切换到新的WAL文件
4.5 WAL配置参数
主要WAL配置参数包括:
wal_level:WAL日志级别(minimal、replica、logical)synchronous_commit:同步提交级别wal_buffers:WAL缓冲区大小checkpoint_timeout:检查点超时时间max_wal_size:检查点之间允许的最大WAL文件大小min_wal_size:检查点后保留的最小WAL文件大小wal_compression:是否压缩WAL记录
4.6 主要文件
src/backend/access/transam/xlog.c:WAL记录和恢复src/backend/access/transam/xloginsert.c:WAL记录插入src/backend/access/transam/xlogutils.c:WAL工具函数src/backend/postmaster/walwriter.c:WAL写入进程
五、检查点机制
检查点是PostgreSQL维护数据一致性的重要机制,它将内存中的脏数据写入磁盘,并更新控制文件和数据文件的头部信息。
5.1 检查点的作用
检查点的主要作用是:
- 减少崩溃恢复时间:崩溃后只需要重放检查点后的WAL日志
- 确保数据一致性:将内存中的脏数据写入磁盘
- 控制WAL文件数量:触发检查点后,旧的WAL文件可以被回收或归档
5.2 检查点类型
PostgreSQL支持两种类型的检查点:
- 自动检查点:由系统自动触发,基于时间或WAL文件大小
- 手动检查点:通过
CHECKPOINT命令手动触发
5.3 检查点流程
检查点的执行流程主要包括:
- 停止接受新的检查点请求
- 通知所有后端进程开始准备检查点
- 等待所有后端进程准备就绪
- 记录检查点开始的WAL记录
- 将所有脏缓冲区写入磁盘
- 更新控制文件和数据文件的头部信息
- 记录检查点完成的WAL记录
- 唤醒等待检查点完成的进程
5.4 检查点配置参数
主要检查点配置参数包括:
checkpoint_timeout:自动检查点之间的最大时间间隔(默认5分钟)max_wal_size:检查点之间允许的最大WAL文件大小(默认1GB)min_wal_size:检查点后保留的最小WAL文件大小(默认80MB)checkpoint_completion_target:检查点完成目标(0.1-1.0,默认0.9)checkpoint_flush_after:检查点刷新后写入的字节数(默认256KB)
5.5 主要文件
src/backend/postmaster/checkpointer.c:检查点进程src/backend/access/transam/xlog.c:检查点实现
六、VACUUM机制
VACUUM是PostgreSQL的垃圾回收机制,用于清理过期的数据版本和回收空闲空间。
6.1 VACUUM的作用
VACUUM的主要作用是:
- 清理过期数据版本:删除被标记为删除的行版本
- 回收空闲空间:将未使用的空间标记为可用
- 更新统计信息:(VACUUM ANALYZE)
- 防止事务ID回绕:确保事务ID不会用尽
6.2 VACUUM类型
PostgreSQL支持以下VACUUM类型:
-
普通VACUUM:
- 不阻塞表的读写操作
- 只标记空闲空间,不收缩表
- 可以在表使用时运行
-
VACUUM FULL:
- 重建表,回收所有空闲空间
- 会阻塞表的读写操作
- 需要较长时间运行
- 会生成大量WAL日志
-
VACUUM ANALYZE:
- 执行VACUUM操作
- 更新表的统计信息
- 推荐定期执行
6.3 VACUUM流程
普通VACUUM的执行流程主要包括:
- 扫描表的所有数据块
- 标记过期的数据版本
- 更新行指针数组
- 标记空闲空间
- 更新可见性映射(Visibility Map)
- 更新冻结信息
6.4 自动VACUUM
PostgreSQL默认启用自动VACUUM机制,由autovacuum进程自动执行VACUUM和ANALYZE操作。
主要自动VACUUM配置参数:
autovacuum:是否启用自动VACUUM(默认on)autovacuum_vacuum_threshold:VACUUM触发阈值(默认50)autovacuum_vacuum_scale_factor:VACUUM触发比例(默认0.2)autovacuum_analyze_threshold:ANALYZE触发阈值(默认50)autovacuum_analyze_scale_factor:ANALYZE触发比例(默认0.1)autovacuum_max_workers:自动VACUUM最大工作进程数(默认3)autovacuum_naptime:自动VACUUM进程唤醒间隔(默认1分钟)
6.5 主要文件
src/backend/postmaster/autovacuum.c:自动VACUUM进程src/backend/commands/vacuum.c:VACUUM命令实现
七、大对象存储
PostgreSQL支持存储大型对象(Large Objects),用于存储超过TOAST阈值(默认2KB)的数据。
7.1 TOAST机制
TOAST(The Oversized-Attribute Storage Technique)是PostgreSQL处理大字段的机制。当字段大小超过TOAST阈值时,会将字段数据压缩或拆分成多个块存储。
TOAST存储策略:
- PLAIN:不允许TOAST,字段大小不能超过页面大小
- EXTENDED:先压缩,再拆分(默认策略)
- EXTERNAL:只拆分,不压缩
- MAIN:只压缩,不拆分
7.2 大对象管理
7.2.1 创建和使用大对象
-- 创建大对象
SELECT lo_create(0); -- 返回大对象OID
-- 写入大对象(使用PL/pgSQL)
DO $$
DECLARE
loid OID;
lfd INTEGER;
BEGIN
loid := lo_create(0);
lfd := lo_open(loid, 131072); -- 131072 = WRITE
lo_write(lfd, 'Large object data');
lo_close(lfd);
RAISE NOTICE 'Created large object with OID: %', loid;
END;
$$;
-- 读取大对象
SELECT lo_get(loid);
-- 删除大对象
SELECT lo_unlink(loid);
7.3 主要文件
src/backend/access/common/tuptoaster.c:TOAST机制实现src/backend/storage/large_object/:大对象支持
八、存储引擎优化
8.1 表空间优化
表空间允许将不同的数据库对象存储在不同的存储设备上,从而优化I/O性能。
-- 创建表空间
CREATE TABLESPACE fast_ssd LOCATION '/mnt/ssd/pgdata';
CREATE TABLESPACE slow_hdd LOCATION '/mnt/hdd/pgdata';
-- 创建表时指定表空间
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100)
) TABLESPACE fast_ssd;
-- 创建索引时指定表空间
CREATE INDEX idx_users_email ON users(email) TABLESPACE fast_ssd;
8.2 数据块大小优化
PostgreSQL默认数据块大小为8KB,可以通过编译选项修改:
./configure --with-blocksize=16
make
make install
注意:修改数据块大小需要重新编译PostgreSQL,且所有数据库集群必须使用相同的数据块大小。
8.3 填充因子优化
填充因子(Fill Factor)控制索引或表的数据块填充程度。
-- 创建表时设置填充因子
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100)
) WITH (fillfactor = 70);
-- 创建索引时设置填充因子
CREATE INDEX idx_users_email ON users(email) WITH (fillfactor = 70);
-- 修改现有表的填充因子
ALTER TABLE users SET (fillfactor = 70);
-- 重建表以应用新的填充因子
VACUUM FULL users;
8.4 主要文件
src/backend/storage/:存储管理src/backend/commands/tablespace.c:表空间管理
九、实战案例:存储引擎性能优化
9.1 案例描述
假设有一个大型电商网站的订单表,数据量超过1亿行,查询性能较慢。
9.2 优化策略
-
使用合适的索引:
-- 为经常查询的列创建B-Tree索引 CREATE INDEX idx_orders_user_id ON orders(user_id); CREATE INDEX idx_orders_created_at ON orders(created_at); -- 为JSONB字段创建GIN索引 CREATE INDEX idx_orders_metadata ON orders USING GIN(metadata); -
分区表:
-- 创建分区表 CREATE TABLE orders ( id SERIAL, user_id INT NOT NULL, product_id INT NOT NULL, amount DECIMAL(10, 2) NOT NULL, created_at TIMESTAMP NOT NULL, status VARCHAR(20) NOT NULL ) PARTITION BY RANGE (created_at); -- 创建分区 CREATE TABLE orders_2023_01 PARTITION OF orders FOR VALUES FROM ('2023-01-01') TO ('2023-02-01'); CREATE TABLE orders_2023_02 PARTITION OF orders FOR VALUES FROM ('2023-02-01') TO ('2023-03-01'); -
使用BRIN索引:
-- 为时间序列数据创建BRIN索引 CREATE INDEX idx_orders_created_at_brin ON orders USING BRIN(created_at); -
优化TOAST存储:
-- 修改表的TOAST存储策略 ALTER TABLE orders ALTER COLUMN metadata SET STORAGE EXTERNAL; -
定期VACUUM和ANALYZE:
-- 手动执行VACUUM ANALYZE VACUUM ANALYZE orders; -- 调整自动VACUUM参数 ALTER TABLE orders SET ( autovacuum_vacuum_scale_factor = 0.01, autovacuum_analyze_scale_factor = 0.005 ); -
使用表空间:
-- 将历史数据存储到低速存储设备 ALTER TABLE orders_2023_01 SET TABLESPACE slow_hdd; ALTER TABLE orders_2023_02 SET TABLESPACE slow_hdd;
9.3 优化效果
通过以上优化策略,可以预期:
- 查询性能提高50%以上
- 索引大小减少30%以上
- VACUUM和ANALYZE时间减少40%以上
- 存储成本降低20%以上
十、总结
PostgreSQL存储引擎是一个复杂而强大的组件,支持多种存储方式和索引类型,能够适应不同的应用场景和工作负载。深入理解PostgreSQL存储引擎的工作原理,对于数据库设计、性能优化和故障诊断都非常重要。
主要存储引擎组件包括:
- 堆表存储:默认的表存储方式,支持MVCC
- 多种索引类型:B-Tree、Hash、GiST、SP-GiST、GIN、BRIN等
- WAL机制:确保数据一致性和可靠性
- 检查点机制:减少崩溃恢复时间
- VACUUM机制:垃圾回收和空间回收
- TOAST机制:处理大字段
- 大对象支持:存储大型数据
通过合理配置和优化存储引擎,可以显著提高PostgreSQL数据库的性能和可靠性。在实际应用中,需要根据具体的工作负载和硬件环境,选择合适的存储策略和优化方法。
通过本章节的学习,读者应该掌握PostgreSQL存储引擎的核心原理和优化方法,能够根据实际需求设计和优化数据库存储结构,提高数据库性能和可靠性。
更多推荐



所有评论(0)