外观
PostgreSQL 存储引擎与表结构
PostgreSQL的存储引擎是数据库管理系统的核心组件,负责数据的存储、检索和管理。与其他数据库系统不同,PostgreSQL采用单一存储引擎架构,但通过丰富的表类型和扩展机制提供了灵活的存储解决方案。
存储引擎概述
PostgreSQL存储引擎特点
PostgreSQL采用基于关系模型的存储引擎,具有以下特点:
- 单一存储引擎架构:PostgreSQL不像MySQL那样支持多种存储引擎,而是采用单一的存储引擎设计
- 基于磁盘的持久化存储:数据持久化存储在磁盘上,确保数据安全性
- 支持多种表类型:通过表访问方法支持不同类型的表,如堆表、索引组织表、外部表等
- 丰富的数据类型支持:支持基本数据类型、复杂数据类型和自定义数据类型
- MVCC并发控制:采用多版本并发控制机制,提高并发处理能力
- 支持扩展:通过扩展机制可以增强存储引擎功能
存储引擎架构
PostgreSQL的存储引擎架构主要包括:
- 访问方法层:提供表和索引的访问接口
- 缓存管理层:管理共享缓冲区和WAL缓冲区
- 存储管理层:负责数据文件的读写操作
- 事务管理层:确保事务的ACID特性
- 并发控制层:处理多用户并发访问
表结构设计
表的基本组成
PostgreSQL表由以下部分组成:
- 表定义:包含表名、列定义、约束等元数据
- 数据文件:存储表的实际数据
- 索引:加速数据检索
- TOAST表:存储大字段数据
- 统计信息:用于查询优化
表的创建与管理
创建表
创建表是数据库设计的基础,PostgreSQL提供了灵活的表创建语法:
sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);修改表
可以使用ALTER TABLE命令修改表结构:
sql
-- 添加列
ALTER TABLE users ADD COLUMN full_name VARCHAR(100);
-- 修改列类型
ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(150);
-- 添加约束
ALTER TABLE users ADD CONSTRAINT users_email_check CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$');
-- 删除列
ALTER TABLE users DROP COLUMN full_name;删除表
使用DROP TABLE命令删除表:
sql
DROP TABLE IF EXISTS users;数据类型选择
选择合适的数据类型对于数据库性能和存储空间至关重要:
数值类型
| 数据类型 | 存储大小 | 范围 | 应用场景 |
|---|---|---|---|
| SMALLINT | 2字节 | -32768 到 32767 | 小整数值,如年龄、数量 |
| INTEGER | 4字节 | -2147483648 到 2147483647 | 常用整数值,如ID、计数 |
| BIGINT | 8字节 | -9223372036854775808 到 9223372036854775807 | 大整数值,如雪花ID、大计数 |
| NUMERIC(precision, scale) | 可变 | 最高131072位精度 | 精确数值,如货币、科学计算 |
| FLOAT4 | 4字节 | 单精度浮点数 | 近似数值,如坐标、测量值 |
| FLOAT8 | 8字节 | 双精度浮点数 | 高精度近似数值 |
字符类型
| 数据类型 | 特点 | 应用场景 |
|---|---|---|
| CHAR(n) | 固定长度,空格填充 | 长度固定的数据,如身份证号、手机号 |
| VARCHAR(n) | 可变长度,最大n个字符 | 长度可变的数据,如用户名、邮箱 |
| TEXT | 可变长度,无限制 | 大文本数据,如文章内容、描述 |
日期时间类型
| 数据类型 | 存储大小 | 范围 | 应用场景 |
|---|---|---|---|
| DATE | 4字节 | 4713 BC 到 5874897 AD | 日期值,如生日、订单日期 |
| TIME | 8字节 | 00:00:00 到 23:59:59.999999 | 时间值,如开始时间、结束时间 |
| TIMESTAMP | 8字节 | 4713 BC 到 5874897 AD | 日期和时间,如创建时间、更新时间 |
| INTERVAL | 16字节 | -178000000 年 到 178000000 年 | 时间间隔,如持续时间 |
布尔类型
- BOOLEAN:存储布尔值(TRUE/FALSE)
- 存储大小:1字节
- 应用场景:标记状态、开关等
复杂数据类型
| 数据类型 | 特点 | 应用场景 |
|---|---|---|
| ARRAY | 存储同类型元素的数组 | 标签、多值属性 |
| JSON/JSONB | 存储JSON数据 | 半结构化数据、配置信息 |
| HSTORE | 存储键值对 | 动态属性、配置数据 |
| RANGE | 存储范围值 | 时间范围、数值范围 |
| ENUM | 存储枚举值 | 状态、类型等有限选项 |
约束设计
约束用于确保数据的完整性和一致性:
主键约束
- 唯一标识表中的每一行
- 自动创建唯一索引
- 不能包含NULL值
sql
-- 创建表时定义主键
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price NUMERIC(10, 2) NOT NULL
);
-- 现有表添加主键
ALTER TABLE products ADD PRIMARY KEY (id);外键约束
- 建立表之间的关系
- 确保引用完整性
- 支持级联操作
sql
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
product_id INTEGER REFERENCES products(id),
quantity INTEGER NOT NULL,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);唯一约束
- 确保列或列组合的值唯一
- 可以包含NULL值
sql
ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);检查约束
- 确保列值满足特定条件
- 支持复杂表达式
sql
ALTER TABLE products ADD CONSTRAINT products_price_check CHECK (price > 0);非空约束
- 确保列不能包含NULL值
sql
ALTER TABLE users ALTER COLUMN username SET NOT NULL;表类型
PostgreSQL支持多种表类型,通过表访问方法实现:
堆表(Heap Table)
堆表是PostgreSQL默认的表类型,具有以下特点:
- 无序存储:数据按插入顺序存储,不保证物理顺序
- 支持MVCC:采用多版本并发控制,每行数据包含版本信息
- 支持全表扫描:适合全表查询操作
- 适合频繁更新:更新操作只需要标记旧版本,插入新版本
索引组织表(Index-Organized Table)
PostgreSQL通过主键索引实现类似索引组织表的功能:
- 基于主键的有序存储:数据按主键顺序存储在索引中
- 提高主键查询性能:主键查询只需要访问索引,不需要回表
- 节省存储空间:不需要单独的堆表存储
外部表(External Table)
外部表允许PostgreSQL访问外部数据源,如文件、其他数据库等:
- 只读访问:默认情况下只能读取外部数据
- 支持多种数据源:文件系统、HDFS、其他数据库等
- 通过扩展实现:如file_fdw、postgres_fdw、jdbc_fdw等
sql
-- 创建外部表扩展
CREATE EXTENSION file_fdw;
-- 创建服务器
CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw;
-- 创建外部表
CREATE FOREIGN TABLE external_orders (
id INTEGER,
name VARCHAR(100),
price NUMERIC(10, 2)
) SERVER file_server
OPTIONS (filename '/path/to/orders.csv', format 'csv', header 'true');分区表
分区表是将大表分割为多个小表的技术,提高查询和维护性能:
- 水平分区:将表按行分割为多个分区
- 支持多种分区策略:范围分区、列表分区、哈希分区、复合分区
- 透明访问:应用程序无需修改即可访问分区表
- 提高查询性能:只扫描相关分区
- 便于维护:可以单独维护分区
sql
-- 创建分区表
CREATE TABLE sales (
id SERIAL,
product_id INTEGER NOT NULL,
sale_date DATE NOT NULL,
amount NUMERIC(10, 2) NOT NULL
) PARTITION BY RANGE (sale_date);
-- 创建分区
CREATE TABLE sales_2023 PARTITION OF sales
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
CREATE TABLE sales_2024 PARTITION OF sales
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');临时表
临时表用于存储会话期间的临时数据:
- 会话隔离:只对创建会话可见
- 自动清理:会话结束或事务提交后自动删除(取决于创建方式)
- 存储在临时表空间:默认存储在pg_temp表空间
sql
-- 创建事务级临时表
CREATE TEMP TABLE temp_users (
id INTEGER,
name VARCHAR(100)
);
-- 创建会话级临时表
CREATE TEMP TABLE session_users (
id INTEGER,
name VARCHAR(100)
) ON COMMIT PRESERVE ROWS;未日志表(Unlogged Table)
未日志表不写入WAL日志,提高写入性能,但数据安全性降低:
- 不写入WAL日志:写入性能比普通表高
- 数据安全性低:崩溃后数据会丢失
- 不支持复制:不能用于复制环境
- 适合临时数据:如缓存、临时结果
sql
CREATE UNLOGGED TABLE cache_data (
key VARCHAR(100) PRIMARY KEY,
value TEXT,
expires_at TIMESTAMP
);表访问方法
PostgreSQL通过表访问方法(Table Access Method)实现不同类型的表:
内置表访问方法
- heap:默认的堆表访问方法
- btree:B树索引访问方法
- hash:哈希索引访问方法
- gist:通用搜索树索引访问方法
- spgist:空间分区通用搜索树索引访问方法
- gin:倒排索引访问方法
- brin:块范围索引访问方法
自定义表访问方法
PostgreSQL支持通过扩展实现自定义表访问方法:
- TimescaleDB:时序数据访问方法
- Citus:分布式表访问方法
- PostGIS:空间数据访问方法
存储优化
表结构优化
合理设计表结构:
- 避免过度规范化
- 合理选择数据类型
- 适当添加约束
优化列顺序:
- 频繁访问的列放在前面
- 固定长度列放在前面
- NULL值较多的列放在后面
避免过度设计:
- 只添加必要的列
- 避免使用过于复杂的数据类型
- 合理使用约束
数据压缩
PostgreSQL支持多种数据压缩方式:
TOAST压缩:
- 自动压缩大字段数据
- 支持四种压缩策略:PLAIN、EXTENDED、EXTERNAL、MAIN
- 默认使用EXTENDED策略(先压缩,再存储到TOAST表)
表压缩:
- PostgreSQL 14+支持表级压缩
- 使用
COMPRESSION选项指定压缩算法 - 支持zstd、pglz等压缩算法
sql
-- 创建压缩表
CREATE TABLE compressed_table (
id SERIAL PRIMARY KEY,
data TEXT
) WITH (COMPRESSION = 'zstd');- 外部表压缩:
- 外部数据可以使用压缩格式
- 如CSV.GZ、Parquet等压缩格式
分区优化
选择合适的分区策略:
- 时间序列数据:使用范围分区
- 离散值数据:使用列表分区
- 均匀分布数据:使用哈希分区
合理设置分区大小:
- 分区大小建议在1GB到10GB之间
- 避免过多小分区
使用分区修剪:
- 确保查询条件包含分区键
- 优化查询计划,只扫描相关分区
索引优化
选择合适的索引类型:
- B树索引:适合范围查询和排序
- 哈希索引:适合等值查询
- GIN索引:适合数组和JSONB数据
- BRIN索引:适合大表的范围查询
合理创建索引:
- 只为频繁查询的列创建索引
- 避免过多索引影响写入性能
- 考虑复合索引,覆盖常用查询
定期维护索引:
- 重建碎片化的索引
- 分析索引使用情况
- 删除无用的索引
TOAST机制
TOAST(The Oversized-Attribute Storage Technique)是PostgreSQL用于存储大字段的机制:
TOAST工作原理
- 当行数据大小超过页大小的25%时,大字段会被TOAST处理
- TOAST处理包括压缩和/或存储到TOAST表
- TOAST表是与主表关联的辅助表,存储大字段数据
- 主表中只存储TOAST指针,指向TOAST表中的实际数据
TOAST存储策略
PostgreSQL支持四种TOAST存储策略:
| 策略 | 描述 | 应用场景 |
|---|---|---|
| PLAIN | 不使用TOAST,大字段直接存储 | 小型字段,不需要压缩 |
| EXTENDED | 先压缩,再存储到TOAST表(默认) | 大文本、JSON数据等 |
| EXTERNAL | 不压缩,直接存储到TOAST表 | 已经压缩的数据,如图片、视频 |
| MAIN | 只压缩,不存储到TOAST表 | 中等大小字段,适合压缩 |
TOAST优化
选择合适的TOAST策略:
- 对于大文本数据,使用EXTENDED策略
- 对于已经压缩的数据,使用EXTERNAL策略
- 对于中等大小字段,使用MAIN策略
监控TOAST使用情况:
- 使用
pg_total_relation_size()查看表和TOAST表的总大小 - 使用
pg_stat_user_tables查看TOAST相关统计信息
- 使用
案例分析:大表性能优化
背景:某电商平台的订单表数据量超过10亿行,查询性能下降明显。
分析:
- 全表扫描时间过长
- 索引大小过大,维护成本高
- 写入性能下降
- 备份和恢复时间过长
优化措施:
实施分区表:
- 按订单日期进行范围分区,每个月一个分区
- 将历史数据分区存储在低成本存储上
- 查询时只扫描相关分区,提高查询性能
优化索引:
- 保留常用查询的索引
- 删除无用的索引
- 为分区表创建本地索引
使用压缩:
- 对历史数据分区启用zstd压缩
- 减少存储空间占用,提高I/O性能
数据归档:
- 将超过2年的历史数据归档到外部表
- 减少主表数据量,提高查询性能
优化查询:
- 确保查询条件包含分区键
- 使用合适的索引
- 避免全表扫描
结果:
- 查询性能提高了10倍
- 写入性能提高了5倍
- 存储空间减少了60%
- 备份和恢复时间缩短了80%
总结
PostgreSQL的存储引擎虽然采用单一架构,但通过丰富的表类型、数据类型和扩展机制提供了灵活的存储解决方案。理解PostgreSQL的存储引擎和表结构设计对于DBA进行数据库设计、性能优化和维护至关重要。
在实际生产环境中,DBA需要根据业务需求和系统资源情况,合理设计表结构,选择合适的表类型和存储策略,优化数据存储,确保数据库系统的高性能和可靠性。
PostgreSQL的存储引擎设计充分考虑了数据的安全性、可靠性和性能,通过不断的技术创新和扩展机制,能够满足从简单应用到复杂企业级系统的各种存储需求。
