PostgreSQL教程FG279-PG大对象存储实战:海量文件存储方案
本文档风哥主要介绍PostgreSQL大对象(Large Object)存储的实战方案,包括大对象的基本操作、管理、性能优化等内容。风哥教程参考PostgreSQL官方文档Large Objects内容,适合需要存储和管理海量文件的企业级应用场景。更多视频教程www.fgedu.net.cn
Part01-基础概念与理论知识
1.1 PostgreSQL大对象的概念
PostgreSQL大对象(Large Object,简称LO)是一种用于存储大型二进制数据的机制,支持存储超过TOAST(The Oversized-Attribute Storage Technique)限制的大型数据。大对象的主要特点:
- 支持存储最大4TB的二进制数据
- 提供流式读写接口,适合处理大型文件
- 支持随机访问,可以在文件的任意位置读写数据
- 提供权限控制,确保数据安全
- 支持事务,确保数据一致性
TOAST适合存储中等大小的二进制数据(最大约1GB),而大对象适合存储更大的二进制数据(最大4TB)。TOAST是自动管理的,而大对象需要显式创建和管理。
1.2 大对象存储原理
PostgreSQL大对象的存储原理:
– 大对象元数据:存储在pg_largeobject_metadata表中
– 大对象数据:存储在pg_largeobject表中,按块存储
– 块大小:默认2KB
– 存储方式:二进制数据被分割成多个块,每个块存储为pg_largeobject表中的一行
# 大对象标识符
– OID(Object Identifier):大对象的唯一标识符
– 通过OID引用和访问大对象
# 大对象操作API
– SQL接口:通过lo_*函数操作大对象
– 客户端接口:通过libpq的大对象API操作
# 大对象事务支持
– 大对象操作在事务中执行
– 支持回滚和提交
– 并发控制:使用共享锁和排他锁
1.3 大对象适用场景
PostgreSQL大对象适用于以下场景:
1. 存储大型文件:如文档、图像、视频、音频等
2. 需要随机访问的大型数据:如数据库备份、科学数据等
3. 需要事务支持的二进制数据操作
4. 需要细粒度权限控制的二进制数据
5. 与数据库其他数据关联的大型文件
# 不适用场景
1. 频繁访问的小型文件(使用TOAST更合适)
2. 需要文件系统级操作的场景
3. 需要直接文件系统访问的场景
4. 极高并发的文件访问场景
Part02-生产环境规划与建议
2.1 大对象存储规划
在实施大对象存储前,需要进行详细的规划:
1. 数据量评估:估算需要存储的大对象数量和大小
2. 访问模式:分析大对象的读写频率和方式
3. 存储策略:确定使用大对象还是文件系统存储
4. 备份策略:制定大对象的备份和恢复方案
5. 性能要求:评估大对象操作的性能需求
# 数据量估算
– 单个大对象大小:从几MB到GB级
– 大对象数量:从几千到几百万
– 总存储需求:根据单个大小和数量估算
# 访问模式分析
– 读多写少:如文档管理系统
– 写多读少:如数据采集系统
– 随机访问:如科学数据处理
– 顺序访问:如视频流服务
2.2 存储设计与配置
大对象存储设计与配置建议:
– 为大对象创建专用表空间
– 使用高性能存储设备
– 考虑使用SSD提高I/O性能
# 数据库配置
– 调整shared_buffers参数
– 配置适当的work_mem
– 调整maintenance_work_mem
– 优化checkpoint参数
# 存储优化
– 合理设置大对象块大小
– 考虑使用压缩(如果适用)
– 定期清理未使用的大对象
# 高可用考虑
– 确保大对象数据包含在备份中
– 配置流复制确保大对象数据同步
– 测试大对象在故障转移后的可用性
2.3 性能考虑因素
大对象存储的性能考虑因素:
- 存储设备:使用高性能存储,如SSD或NVMe
- 网络带宽:确保足够的网络带宽,特别是在客户端与数据库分离的场景
- 内存配置:增加shared_buffers和work_mem以提高大对象操作性能
- 连接池:使用连接池减少连接开销
- 批量操作:对于大量大对象操作,使用批量处理减少开销
Part03-生产环境项目实施方案
3.1 大对象基本操作
3.1.1 创建和管理大对象
$ psql -U pgsql -d fgedudb
— 创建大对象并返回OID
SELECT lo_create(0);
lo_create
———–
123456
(1 row)
— 从文件创建大对象
SELECT lo_import(‘/path/to/file.jpg’);
lo_import
———–
123457
(1 row)
— 导出大对象到文件
SELECT lo_export(123457, ‘/path/to/output.jpg’);
lo_export
———–
1
(1 row)
— 删除大对象
SELECT lo_unlink(123456);
lo_unlink
———–
1
(1 row)
— 检查大对象是否存在
SELECT lo_exists(123457);
lo_exists
———–
t
(1 row)
3.1.2 大对象读写操作
— 打开大对象(模式:r=读, w=写, x=读写)
SELECT lo_open(123457, 2); — 2=写模式
lo_open
———-
1
(1 row)
— 写入数据
SELECT lowrite(1, ‘Hello, Large Object!’);
lowrite
———-
18
(1 row)
— 关闭大对象
SELECT lo_close(1);
lo_close
———–
1
(1 row)
— 读取大对象
SELECT loread(lo_open(123457, 1), 100);
loread
——————-
Hello, Large Object!
(1 row)
— 定位到指定位置
SELECT lo_lseek(lo_open(123457, 1), 7, 0); — 0=从开始位置
lo_lseek
———-
7
(1 row)
— 读取从指定位置开始的数据
SELECT loread(lo_open(123457, 1), 10);
loread
———-
Large Obj
(1 row)
3.2 大对象管理
3.2.1 大对象元数据管理
SELECT * FROM pg_largeobject_metadata;
lomowner | lomacl | loid
———-+——–+——
10 | | 123457
(1 row)
# 查看大对象数据块
SELECT * FROM pg_largeobject WHERE loid = 123457;
loid | pageno | data
——-+——–+————————
123457 | 0 | \x48656c6c6f2c204c6172…
(1 row)
# 查看大对象大小
SELECT lo_size(123457);
lo_size
———
18
(1 row)
# 大对象权限管理
— 授予权限
SELECT lo_grant(123457, ‘fgedu’, 2); — 2=写权限
— 撤销权限
SELECT lo_revoke(123457, ‘fgedu’, 2);
3.2.2 大对象清理
SELECT DISTINCT loid
FROM pg_largeobject_metadata
WHERE loid NOT IN (
SELECT DISTINCT lo
FROM (
SELECT unnest(lo_from_bytea(0, data)) AS lo
FROM pg_largeobject
) AS l
);
# 清理未引用的大对象
— 创建清理函数
CREATE OR REPLACE FUNCTION clean_unused_large_objects()
RETURNS INTEGER AS $$
DECLARE
loid_oid OID;
cnt INTEGER := 0;
BEGIN
FOR loid_oid IN
SELECT DISTINCT loid
FROM pg_largeobject_metadata
WHERE loid NOT IN (
SELECT DISTINCT lo
FROM (
SELECT unnest(lo_from_bytea(0, data)) AS lo
FROM pg_largeobject
) AS l
)
LOOP
PERFORM lo_unlink(loid_oid);
cnt := cnt + 1;
END LOOP;
RETURN cnt;
END;
$$ LANGUAGE plpgsql;
— 执行清理
SELECT clean_unused_large_objects();
clean_unused_large_objects
—————————
5
(1 row)
3.3 应用集成方案
3.3.1 应用程序集成
import psycopg2
from psycopg2.extensions import adapt
# 连接数据库
conn = psycopg2.connect(
fgedudb=”fgedudb”,
fgedu=”fgedu”,
password=”password”,
fgedu.net.cn=”localfgedu.net.cn”
)
cur = conn.cursor()
# 上传文件到大对象
def upload_file(file_path):
with open(file_path, ‘rb’) as f:
data = f.read()
cur.execute(“SELECT lo_create(0)”)
loid = cur.fetchone()[0]
cur.execute(“SELECT lo_open(%s, 131072)”, (loid,)) # 131072=读写模式
fd = cur.fetchone()[0]
cur.execute(“SELECT lowrite(%s, %s)”, (fd, data))
cur.execute(“SELECT lo_close(%s)”, (fd,))
conn.commit()
return loid
# 下载大对象到文件
def download_file(loid, file_path):
cur.execute(“SELECT lo_open(%s, 262144)”, (loid,)) # 262144=只读模式
fd = cur.fetchone()[0]
cur.execute(“SELECT loread(%s, 1048576)”, (fd,)) # 读取1MB
data = cur.fetchone()[0]
cur.execute(“SELECT lo_close(%s)”, (fd,))
with open(file_path, ‘wb’) as f:
f.write(data)
# 使用示例
loid = upload_file(‘/path/to/file.jpg’)
print(f”Uploaded file with OID: {loid}”)
download_file(loid, ‘/path/to/output.jpg’)
print(“File downloaded successfully”)
# 关闭连接
cur.close()
conn.close()
3.3.2 大对象与应用数据关联
CREATE TABLE fgedu_files (
id SERIAL PRIMARY KEY,
filename VARCHAR(255) NOT NULL,
filetype VARCHAR(100) NOT NULL,
filesize BIGINT NOT NULL,
lo_oid OID NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(100) NOT NULL
);
# 插入文件记录
INSERT INTO fgedu_files (filename, filetype, filesize, lo_oid, created_by)
VALUES (‘document.pdf’, ‘fgapplication/pdf’, 1048576, 123457, ‘fgedu’);
# 查询文件信息
SELECT * FROM fgedu_files WHERE id = 1;
id | filename | filetype | filesize | lo_oid | created_at | created_by
—-+————-+——————-+———-+——–+—————————-+————
1 | document.pdf | fgapplication/pdf | 1048576 | 123457 | 2026-04-02 10:00:00.000000 | fgedu
# 检索文件
SELECT lo_export(lo_oid, ‘/tmp/’ || filename)
FROM fgedu_files
WHERE id = 1;
# 删除文件及其大对象
BEGIN;
DELETE FROM fgedu_files WHERE id = 1;
SELECT lo_unlink(123457);
COMMIT;
Part04-生产案例与实战讲解
4.1 大对象存储实战
4.1.1 文档管理系统
# 1. 数据库设计
CREATE TABLE fgedu_documents (
id SERIAL PRIMARY KEY,
document_name VARCHAR(255) NOT NULL,
document_type VARCHAR(100) NOT NULL,
document_size BIGINT NOT NULL,
lo_oid OID NOT NULL,
author VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
# 2. 上传文档函数
CREATE OR REPLACE FUNCTION upload_document(
p_name VARCHAR(255),
p_type VARCHAR(100),
p_size BIGINT,
p_data BYTEA,
p_author VARCHAR(100)
) RETURNS INTEGER AS $$
DECLARE
v_oid OID;
v_id INTEGER;
BEGIN
— 创建大对象
v_oid := lo_from_bytea(0, p_data);
— 插入文档记录
INSERT INTO fgedu_documents (
document_name, document_type, document_size, lo_oid, author
) VALUES (
p_name, p_type, p_size, v_oid, p_author
) RETURNING id INTO v_id;
RETURN v_id;
END;
$$ LANGUAGE plpgsql;
# 3. 下载文档函数
CREATE OR REPLACE FUNCTION download_document(p_id INTEGER) RETURNS BYTEA AS $$
DECLARE
v_oid OID;
v_data BYTEA;
BEGIN
— 获取大对象OID
SELECT lo_oid INTO v_oid FROM fgedu_documents WHERE id = p_id;
— 读取大对象数据
v_data := lo_get(v_oid);
RETURN v_data;
END;
$$ LANGUAGE plpgsql;
# 4. 删除文档函数
CREATE OR REPLACE FUNCTION delete_document(p_id INTEGER) RETURNS BOOLEAN AS $$
DECLARE
v_oid OID;
BEGIN
— 获取大对象OID
SELECT lo_oid INTO v_oid FROM fgedu_documents WHERE id = p_id;
— 删除文档记录
DELETE FROM fgedu_documents WHERE id = p_id;
— 删除大对象
PERFORM lo_unlink(v_oid);
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;
# 5. 使用示例
— 上传文档
SELECT upload_document(‘Annual Report.pdf’, ‘fgapplication/pdf’, 2097152, pg_read_binary_file(‘/path/to/report.pdf’), ‘admin’);
— 下载文档
SELECT download_document(1);
— 删除文档
SELECT delete_document(1);
4.1.2 图像存储系统
# 1. 数据库设计
CREATE TABLE fgedu_products (
id SERIAL PRIMARY KEY,
product_name VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE fgedu_product_images (
id SERIAL PRIMARY KEY,
product_id INTEGER REFERENCES fgedu_products(id) ON DELETE CASCADE,
image_name VARCHAR(255) NOT NULL,
image_type VARCHAR(50) NOT NULL,
image_size BIGINT NOT NULL,
lo_oid OID NOT NULL,
is_primary BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
# 2. 插入产品图片
— 插入产品
INSERT INTO fgedu_products (product_name, description, price)
VALUES (‘Smartphone’, ‘Latest model’, 999.99);
— 插入产品图片
INSERT INTO fgedu_product_images (product_id, image_name, image_type, image_size, lo_oid, is_primary)
VALUES (1, ‘front.jpg’, ‘image/jpeg’, 1048576, lo_import(‘/path/to/front.jpg’), TRUE);
INSERT INTO fgedu_product_images (product_id, image_name, image_type, image_size, lo_oid)
VALUES (1, ‘back.jpg’, ‘image/jpeg’, 1048576, lo_import(‘/path/to/back.jpg’));
# 3. 获取产品图片
— 获取产品的主图片
SELECT lo_get(lo_oid) AS image_data
FROM fgedu_product_images
WHERE product_id = 1 AND is_primary = TRUE;
— 获取产品的所有图片
SELECT image_name, image_type, lo_get(lo_oid) AS image_data
FROM fgedu_product_images
WHERE product_id = 1;
# 4. 清理产品图片
— 删除产品(级联删除相关图片)
DELETE FROM fgedu_products WHERE id = 1;
— 注意:需要手动删除大对象
CREATE OR REPLACE FUNCTION cleanup_product_images()
RETURNS TRIGGER AS $$
BEGIN
— 删除关联的大对象
FOR i IN SELECT lo_oid FROM fgedu_product_images WHERE product_id = OLD.id LOOP
PERFORM lo_unlink(i.lo_oid);
END LOOP;
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_delete_product
BEFORE DELETE ON fgedu_products
FOR EACH ROW
EXECUTE FUNCTION cleanup_product_images();
4.2 大对象性能优化
4.2.1 存储优化
# 1. 使用专用表空间
— 创建大对象专用表空间
CREATE TABLESPACE lo_ts LOCATION ‘/postgresql/large_objects’;
— 将大对象表移动到专用表空间
ALTER TABLE pg_largeobject SET TABLESPACE lo_ts;
ALTER TABLE pg_largeobject_metadata SET TABLESPACE lo_ts;
# 2. 调整块大小
— 查看当前块大小
SHOW block_size;
— 注意:块大小在初始化数据库时设置,无法在运行时修改
— 建议在初始化数据库时根据大对象大小设置合适的块大小
# 3. 批量操作
— 批量导入大对象
CREATE OR REPLACE FUNCTION batch_import_files(file_paths TEXT[])
RETURNS TABLE(oid OID, path TEXT) AS $$
DECLARE
file_path TEXT;
BEGIN
FOREACH file_path IN ARRAY file_paths LOOP
RETURN QUERY SELECT lo_import(file_path), file_path;
END LOOP;
END;
$$ LANGUAGE plpgsql;
— 使用示例
SELECT * FROM batch_import_files(ARRAY[‘/path/to/file1.jpg’, ‘/path/to/file2.jpg’]);
# 4. 并行处理
— 使用并行查询处理大对象
ALTER TABLE fgedu_files ADD COLUMN processed BOOLEAN DEFAULT FALSE;
— 并行更新处理状态
UPDATE fgedu_files SET processed = TRUE WHERE id IN (
SELECT id FROM fgedu_files WHERE processed = FALSE ORDER BY id LIMIT 100
);
4.2.2 访问优化
# 1. 使用连接池
— 配置pgBouncer或其他连接池
— 减少连接建立和关闭的开销
# 2. 缓存策略
— 实现应用级缓存
— 缓存频繁访问的大对象
# 3. 异步操作
— 使用异步I/O
— 避免阻塞主线程
# 4. 分段读取
— 对于大型大对象,使用分段读取
— 减少内存使用
# 5. 索引优化
— 为关联表创建合适的索引
CREATE INDEX idx_fgedu_files_created_at ON fgedu_files(created_at);
CREATE INDEX idx_fgedu_files_lo_oid ON fgedu_files(lo_oid);
# 6. 预读取
— 对于顺序访问的场景,实现预读取
— 提高读取性能
4.3 大对象常见问题处理
4.3.1 常见错误及解决方法
# 错误信息:ERROR: permission denied for large object 123456
# 解决方法:
– 确保用户有大对象操作权限
– 使用lo_grant授予权限
– 检查pg_largeobject_metadata表的权限
# 2. 大对象不存在
# 错误信息:ERROR: large object 123456 does not exist
# 解决方法:
– 检查大对象OID是否正确
– 确保大对象未被删除
– 验证引用完整性
# 3. 存储空间不足
# 错误信息:ERROR: could not extend file “base/16384/123456”: No space left on device
# 解决方法:
– 检查磁盘空间
– 清理未使用的大对象
– 扩展存储容量
# 4. 大对象操作超时
# 错误信息:ERROR: canceling statement due to statement timeout
# 解决方法:
– 增加statement_timeout参数
– 优化大对象操作
– 考虑使用分段操作
# 5. 大对象数据损坏
# 错误信息:ERROR: invalid large object descriptor
# 解决方法:
– 检查大对象的完整性
– 从备份恢复
– 实现数据验证机制
4.3.2 大对象监控
# 1. 监控大对象数量
SELECT count(*) FROM pg_largeobject_metadata;
# 2. 监控大对象大小
SELECT sum(lo_size(loid)) FROM pg_largeobject_metadata;
# 3. 监控大对象表空间使用
SELECT pg_size_pretty(pg_fgedutbs_size(‘lo_ts’));
# 4. 监控大对象操作
— 启用审计日志
— 监控lo_*函数的使用
# 5. 监控未引用的大对象
SELECT count(*) FROM pg_largeobject_metadata
WHERE loid NOT IN (
SELECT DISTINCT lo_oid FROM fgedu_files
);
# 6. 监控大对象访问性能
— 使用pg_stat_statements监控大对象相关SQL
SELECT query, calls, total_exec_time
FROM pg_stat_statements
WHERE query LIKE ‘%lo_%’
ORDER BY total_exec_time DESC;
Part05-风哥经验总结与分享
5.1 大对象存储最佳实践
PostgreSQL大对象存储最佳实践:
- 合理规划:根据数据量和访问模式选择合适的存储方案
- 专用表空间:为大对象创建专用表空间,提高性能和管理效率
- 引用完整性:建立大对象与应用数据的关联,确保数据一致性
- 定期清理:清理未使用的大对象,释放存储空间
- 备份策略:确保大对象数据包含在备份中
- 性能优化:根据实际使用情况优化存储和访问策略
- 监控机制:建立大对象使用情况的监控机制
- 权限管理:合理设置大对象的访问权限,确保数据安全
5.2 大对象实施检查清单
## 规划阶段
– [ ] 评估数据量和访问模式
– [ ] 选择存储方案(大对象 vs 文件系统)
– [ ] 设计表结构和关联关系
– [ ] 规划存储容量和增长策略
## 实施阶段
– [ ] 创建专用表空间(可选)
– [ ] 配置数据库参数
– [ ] 实现大对象操作函数
– [ ] 建立应用集成方案
– [ ] 测试大对象操作性能
## 管理阶段
– [ ] 定期清理未使用的大对象
– [ ] 监控大对象使用情况
– [ ] 备份大对象数据
– [ ] 优化大对象存储和访问
– [ ] 建立大对象操作的审计机制
## 安全阶段
– [ ] 设置大对象访问权限
– [ ] 加密敏感大对象数据
– [ ] 实现数据验证机制
– [ ] 建立访问控制策略
5.3 大对象工具推荐
PostgreSQL大对象相关工具推荐:
- pg_lo_import/pg_lo_export:命令行工具,用于导入/导出大对象
- psycopg2:Python库,支持大对象操作
- libpq:C语言库,提供大对象API
- JDBC:Java库,支持大对象操作
- pgAdmin:图形化工具,支持大对象管理
- Barman:备份工具,支持大对象备份
- pgBackRest:备份工具,支持大对象备份和恢复
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
