16 数据库准备
数据库准备
RAG 数据库准备的核心是把原始文档、文档片段、向量和任务状态分开存,让后面的上传、解析、切片、Embedding 和检索都有稳定的数据落点。
[TOC]
本节要准备什么
RAG 不是只有一个向量库。
至少要准备几类数据:
原始文档信息
文件存储位置
解析后的文本
切片后的 chunkchunk embedding
处理状态
错误信息
本节的目标是准备数据库结构。
先不急着做检索。
先让后面的流程有地方存数据。
RAG 数据落点可以先这样理解:
graph TD;
A["上传文件"] --> B["rag_document<br/>原始文档元数据"];
A --> C["文件系统 / 对象存储<br/>原始文件"];
B --> D["rag_document_text<br/>raw_text / cleaned_text"]; D --> E["rag_chunk<br/>chunk 文本 / metadata"]; E --> F["embedding 字段<br/>vector 类型"];
B --> G["rag_task<br/>解析 / 清洗 / 切片 / 向量化任务"];
G --> D; G --> E; E --> H["向量检索<br/>topK chunks"];
数据库分工
学习阶段可以这样理解:
PostgreSQL:RAG 主库,存文档、chunk、向量
pgvector:PostgreSQL 的向量扩展
MySQL:业务系统已有数据时可继续使用
Redis:缓存、任务进度、会话状态
第一版 RAG Demo 建议:
PostgreSQL + pgvector 作为主线
Redis 可选
MySQL 暂时不强依赖
原因是:
少一个组件,少一层复杂度
pgvector 能同时存结构化字段和向量
SQL 对 Java 后端更友好
启动 PostgreSQL + pgvector
可以使用 Docker Compose。
创建 docker-compose.yml:
services:
postgres: image: pgvector/pgvector:pg16 container_name: ai-rag-postgres environment: POSTGRES_DB: ai_rag POSTGRES_USER: ai POSTGRES_PASSWORD: ai_password ports: - "5432:5432" volumes: - ai_rag_pg_data:/var/lib/postgresql/data
volumes:
ai_rag_pg_data:
启动:
docker compose up -d
连接:
psql postgresql://ai:ai_password@127.0.0.1:5432/ai_rag```
启用扩展:
```sql
CREATE EXTENSION IF NOT EXISTS vector;
检查:
SELECT extname, extversion
FROM pg_extension
WHERE extname = 'vector';
文档表
文档表存一份原始文件的元数据。
CREATE TABLE rag_document (
id uuid PRIMARY KEY, original_name varchar(512) NOT NULL, storage_path varchar(1024) NOT NULL, content_type varchar(128), file_size bigint NOT NULL, sha256 varchar(128), status varchar(32) NOT NULL, error_message text, created_at timestamp NOT NULL DEFAULT now(), updated_at timestamp NOT NULL DEFAULT now());
字段含义:
id:文档 IDoriginal_name:用户上传时的文件名
storage_path:服务端保存路径
content_type:文件类型
file_size:文件大小
sha256:文件 hash,用于去重
status:处理状态
error_message:失败原因
created_at / updated_at:时间
状态可以先定义:
UPLOADED:已上传
PARSED:已解析
CLEANED:已清洗
CHUNKED:已切片
EMBEDDED:已向量化
FAILED:处理失败
解析文本表
解析后的全文可以单独存。
CREATE TABLE rag_document_text (
document_id uuid PRIMARY KEY REFERENCES rag_document(id) ON DELETE CASCADE, raw_text text NOT NULL, cleaned_text text, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamp NOT NULL DEFAULT now(), updated_at timestamp NOT NULL DEFAULT now());
为什么不全塞进 rag_document?
因为原始文档信息和大段文本生命周期不一样。
分开后更清楚:
rag_document:文件级元数据
rag_document_text:解析和清洗后的文本
Chunk 表
Chunk 表存切片结果。
CREATE TABLE rag_chunk (
id uuid PRIMARY KEY, document_id uuid NOT NULL REFERENCES rag_document(id) ON DELETE CASCADE, chunk_index int NOT NULL, content text NOT NULL, title_path text, page_start int, page_end int, char_start int, char_end int, token_count int, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamp NOT NULL DEFAULT now(), updated_at timestamp NOT NULL DEFAULT now(), UNIQUE (document_id, chunk_index));
字段含义:
chunk_index:当前文档里的第几个片段
content:片段正文
title_path:标题路径,比如 产品手册 > 上传限制
page_start / page_end:页码范围
char_start / char_end:字符位置
token_count:估算 token 数
metadata:扩展信息
先把 chunk 文本存好。
Embedding 可以后面再补。
向量字段
如果已经确定 embedding 维度,可以在 chunk 表加向量字段。
示例:
ALTER TABLE rag_chunk
ADD COLUMN embedding vector(1536);
注意:
1536 只是示例维度
真实维度必须和你使用的 Embedding 模型一致
如果模型维度是 1024,就要用:
ALTER TABLE rag_chunk
ADD COLUMN embedding vector(1024);
维度不一致,插入会失败。
学习阶段可以先不加 embedding,等后面确认模型后再加。
如果想先准备好,就在文档里标注清楚:
TODO:根据 Embedding 模型确认 vector 维度
向量索引
pgvector 常见相似度:
L2 distance
Inner product
Cosine distance
RAG 文本检索里常用 cosine。
HNSW 索引示例:
CREATE INDEX rag_chunk_embedding_hnsw_idx
ON rag_chunk
USING hnsw (embedding vector_cosine_ops);
注意:
先有 embedding 字段
再建索引
大批量导入时,通常先导入数据,再建索引
如果数据量很小,不建索引也能跑。
但学习阶段可以提前知道索引写法。
Metadata 过滤索引
RAG 常常需要按文档、用户、知识库过滤。
可以先给常用字段建索引:
CREATE INDEX rag_chunk_document_id_idx
ON rag_chunk (document_id);
如果 metadata 里存很多查询条件,可以用 GIN:
CREATE INDEX rag_chunk_metadata_gin_idx
ON rag_chunk
USING gin (metadata);
但第一版不要把所有东西都塞进 metadata。
能结构化成字段的,就用字段。
metadata 适合放扩展信息:
section
language
parser
source_page_label
任务表
文档上传后,解析、清洗、切片、Embedding 可能是异步任务。
可以准备任务表:
CREATE TABLE rag_ingest_task (
id uuid PRIMARY KEY, document_id uuid NOT NULL REFERENCES rag_document(id) ON DELETE CASCADE, task_type varchar(64) NOT NULL, status varchar(32) NOT NULL, error_message text, started_at timestamp, finished_at timestamp, created_at timestamp NOT NULL DEFAULT now(), updated_at timestamp NOT NULL DEFAULT now());
任务类型:
PARSE
CLEAN
CHUNK
EMBED
任务状态:
PENDING
RUNNING
SUCCESS
FAILED
第一版也可以同步处理。
但提前知道任务表怎么设计,后面更容易扩展。
Redis 用在哪里
Redis 在第一版不是必须。
适合放:
短期任务进度
接口限流计数
会话缓存
热点问答缓存
分布式锁
Docker Compose 可选加入:
redis: image: redis:7 container_name: ai-rag-redis ports: - "6379:6379"```
但如果你现在只想跑通 RAG 主链路,可以先不用 Redis。
## MySQL 用在哪里
如果现有业务系统已经用 MySQL,可以继续让 MySQL 存:
```text
用户
权限
订单
业务配置
菜单
审计记录
RAG 的文档、chunk、embedding 可以放 PostgreSQL。
也可以全放 PostgreSQL。
第一版不要强行同时用两个关系型数据库。
系统越简单,越容易把 RAG 主链路跑通。
初始化脚本
建议把 SQL 放到:
db/init/001_rag_schema.sql
内容顺序:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE rag_document (...);
CREATE TABLE rag_document_text (...);
CREATE TABLE rag_chunk (...);
CREATE TABLE rag_ingest_task (...);
CREATE INDEX ...;
后面可以用 Flyway 或 Liquibase 管理。
学习阶段先用 SQL 文件手动执行也可以。
常见问题
为什么不用纯向量数据库
可以用。
但学习阶段 PostgreSQL + pgvector 更容易理解:
结构化字段
SQL 查询
metadata 过滤
向量搜索
都在一个数据库里
后面数据量大了,再评估 Milvus、Qdrant 等专业向量库。
embedding 维度不知道怎么办
先不要建 vector 字段。
等后面确定 Embedding 模型后再加。
维度必须匹配模型输出。
文档原文件存数据库还是文件系统
第一版建议原文件存文件系统。
数据库存路径和元数据。
原因:
实现简单
数据库压力小
后面换对象存储也方便
正式项目可以用对象存储,比如 MinIO、OSS、S3。
是否要存 raw_text 和 cleaned_text
建议存。
这样方便排查:
解析是否丢内容
清洗是否误删
切片是否合理
RAG 调试离不开中间结果。
练习清单
完成几件事情:
启动 PostgreSQL + pgvector创建 ai_rag 数据库
启用 vector 扩展
创建 rag_document 表
创建 rag_document_text 表
创建 rag_chunk 表
创建 rag_ingest_task 表
了解 embedding vector 维度要求
了解 HNSW cosine 索引写法
能解释 Redis 在 RAG 里的可选作用
建议目录:
ai-agent-study
├── db
│ └── init
│ └── 001_rag_schema.sql
├── docker-compose.yml
└── docs
└── database-prepare.md
小结
本节的结论:
RAG 数据库要先把文档、文本、chunk、向量和任务状态分清楚,后面的流程才不会乱。
最小数据链路:
rag_document
-> rag_document_text -> rag_chunk -> embedding vector
第一版优先跑通 PostgreSQL + pgvector。
Redis 和 MySQL 可以按业务需要再接。