如果你的数据、权限和业务过滤条件都在 PostgreSQL 中,直接引入独立向量数据库未必更合理。
向量检索的难点不是把文本变成向量,而是让近似搜索、关系过滤、数据更新和运维一致性同时成立。
本文以 PostgreSQL 17、pgvector 0.8.x 和 Kubernetes Operator 为语境,分析它适合什么、不适合什么,以及如何落地。
一、向量检索解决的不是“相似度计算”
传统数据库擅长精确条件:
WHERE tenant_id = $1 AND status = 'published'
但 AI 应用经常需要表达“语义相近”:
- 用户问题与哪几段文档最相关;
- 一张图片与哪些商品相似;
- 一段代码与哪些历史代码片段相近;
- 一个工单与哪些已解决案例类似。
Embedding 模型会把对象转换成固定维度的向量,向量检索再依据距离函数返回近邻。常见距离包括:
- 余弦距离(Cosine Distance):关注向量方向,适合多数文本语义检索;
- 内积(Inner Product):常用于已归一化向量;
- 欧氏距离(Euclidean Distance):关注空间中的绝对距离。
真实业务通常不是单纯的相似度排序,而是:
在某个租户、权限范围、时间窗口和业务状态内,找最相似的内容。
这正是 PostgreSQL 的优势所在:向量是新字段,但租户、权限、状态、时间和关联关系仍然可以使用成熟的关系模型表达。
二、核心概念:从精确检索到近似检索
1. 向量列不是“自动智能化”
pgvector 提供 vector(n) 类型,n 是向量维度。维度必须与 Embedding 模型输出一致,否则无法插入或比较。
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE document_chunk (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tenant_id bigint NOT NULL,
document_id bigint NOT NULL,
content text NOT NULL,
embedding vector(1536) NOT NULL,
status text NOT NULL DEFAULT 'published',
created_at timestamptz NOT NULL DEFAULT now()
);
结论:向量维度和模型版本必须被当作数据契约。
机制在于,向量数据库只负责存储和距离计算,并不知道 1536 个数值代表什么。更换 Embedding 模型时,即使模型名称相近,只要维度、归一化方式或语义分布发生变化,旧向量和新向量混用就会造成检索质量下降。
工程上应记录:
- embedding 模型标识;
- 向量维度;
- 分块策略;
- 归一化规则;
- 生成时间和数据版本。
2. 精确近邻与近似近邻有不同边界
没有索引时,可以按距离排序:
SELECT id, content,
embedding <=> $1::vector AS distance
FROM document_chunk
WHERE tenant_id = $2
AND status = 'published'
ORDER BY embedding <=> $1::vector
LIMIT 10;
这是精确近邻搜索(Exact Nearest Neighbor),结果质量稳定,但需要扫描候选行。数据量较小时,它反而是最容易验证的基线。
数据量增大后,需要近似最近邻(Approximate Nearest Neighbor,ANN)索引。pgvector 主要提供:
- HNSW(Hierarchical Navigable Small World):查询质量和延迟通常较好,但建索引耗时、内存占用更高;
- IVFFlat(Inverted File with Flat Compression):建索引和存储成本较低,但需要训练聚类中心,参数和数据分布更敏感。
结论:索引不是越复杂越好,应先用精确搜索建立质量基线,再选择 ANN。
例如,对几万条文档分块,精确搜索可能已经足够;当查询延迟或 CPU 成本无法接受时,再评估 HNSW。没有基线就直接调 ef_search,只能知道“变快或变慢”,不知道召回率损失了多少。
三、关键机制:HNSW 如何与业务过滤共同工作
HNSW 通过多层图结构寻找近邻。构建时,m 控制每个节点的连接数量,ef_construction 控制建图时的候选范围;查询时,ef_search 控制搜索候选规模。
CREATE INDEX document_chunk_embedding_hnsw
ON document_chunk
USING hnsw (embedding vector_cosine_ops)
WITH (
m = 16,
ef_construction = 64
);
查询时可以调整:
BEGIN;
-- 候选范围越大,通常召回率越好,但延迟和 CPU 成本也会上升
SET LOCAL hnsw.ef_search = 100;
SELECT id, document_id, content,
1 - (embedding <=> $1::vector) AS similarity
FROM document_chunk
WHERE tenant_id = $2
AND status = 'published'
ORDER BY embedding <=> $1::vector
LIMIT 20;
COMMIT;
结论:向量索引负责“找近邻”,关系过滤负责“确认候选是否合法”,两者不是同一层能力。
这里存在一个常见边界:如果 tenant_id 的过滤选择性很高,HNSW 找到的近邻可能大部分属于其他租户,数据库过滤后只剩少量结果,甚至不足 LIMIT。pgvector 0.8.x 引入了迭代扫描相关能力,用于在过滤后继续扩展搜索范围,但它不能替代合理的数据分布设计。
例如,以下两种设计的成本不同:
- 一个共享表,所有租户混在一起,再用
tenant_id过滤; - 按租户或业务域进行分区,使向量搜索只在相关分区中发生。
分区不是默认答案。租户数量极多且数据量差异明显时,分区管理、索引数量和查询规划都会变复杂。应通过实际过滤选择性、数据规模和租户增长曲线决定。
四、PostgreSQL 17 + pgvector 0.8.x 的工程评价
本文选用 PostgreSQL 17 作为关系数据库版本,pgvector 0.8.x 作为向量扩展语境。具体可用特性仍需以目标发行版和扩展安装包为准,尤其是托管数据库或 Operator 提供的镜像。
| 方案 | 优势 | 主要代价 | 更适合 |
|---|---|---|---|
| PostgreSQL 精确搜索 | 结果可解释、无需索引训练、事务一致 | 大表扫描成本高 | 原型、低数据量、质量基线 |
| PostgreSQL + HNSW | 查询延迟和召回率通常较好,使用简单 | 建索引耗时、内存占用较高 | 中等规模、强过滤、需要事务一致 |
| PostgreSQL + IVFFlat | 资源成本相对可控,索引构建较轻 | 需要训练,数据分布变化会影响效果 | 数据规模较大、可接受调参 |
| 独立向量数据库 | 面向大规模 ANN 和分布式扩展 | 数据同步、权限、事务和运维链路变复杂 | 超大规模、向量服务独立扩展 |
明确结论:如果业务主数据已经在 PostgreSQL,且需要强事务、权限过滤和联表,优先验证 pgvector;只有当向量检索的吞吐和扩展需求明显超过关系库边界时,再引入独立系统。
需要注意,不能仅用“向量条数”决定选型。还应同时评估:
- 每秒查询数和延迟目标;
- 向量维度及单行大小;
- 过滤条件的选择性;
- 写入、更新和删除频率;
- 索引构建期间的资源峰值;
- 是否需要跨节点水平扩展;
- 是否能接受异步数据同步。
五、Go 与 Kubernetes Operator 的落地方式
Go 1.24 服务应将“向量生成”和“向量检索”拆成两个可独立观测的阶段:
- 文档变更进入队列;
- Worker 调用 Embedding 服务;
- Worker 写入向量及模型元数据;
- 查询服务生成问题向量;
- PostgreSQL 执行带业务过滤的 ANN 查询;
- 可选地使用重排模型对前几十条候选重新排序。
写入时不要把向量拼接进 SQL 字符串,使用参数绑定和 pgvector 驱动支持,避免注入风险与序列化错误。Go 侧还应限制单次批量写入大小,避免 Embedding 服务或数据库连接池被突发任务耗尽。
在 Kubernetes 中,Operator 负责的是 PostgreSQL 集群生命周期,不是自动解决向量质量问题。以 CloudNativePG 这类 PostgreSQL Operator 为例,应重点配置:
- PostgreSQL 17 镜像与 pgvector 扩展版本匹配;
- 主从复制和备份策略;
- PVC 类型与 IOPS;
- 资源请求,尤其是 HNSW 建索引时的内存;
- 扩展安装和升级流程;
- 连接池、Pod 反亲和与故障切换。
结论:Operator 能降低数据库集群运维成本,但不能替代索引压测、召回率评估和模型版本治理。
六、最容易踩坑的地方
误区:先加 ANN 索引,再用业务指标证明效果
触发条件是:团队只观察查询耗时,没有建立精确搜索基线。
典型现象包括:
- 查询很快,但正确文档经常排不到前面;
- 加上
tenant_id、权限和状态过滤后,返回结果数量不足; - 调大
ef_search后延迟突然升高; - Embedding 模型切换后,索引仍然可用,但召回质量持续下降。
规避方式是建立固定评测集,至少记录:
- Recall@K;
- MRR 或 NDCG;
- P95、P99 查询延迟;
- 过滤后有效结果数;
- 不同租户和数据规模下的资源消耗。
先运行精确搜索得到质量上限,再比较 HNSW 与 IVFFlat。数据库层面的“更快”必须和检索层面的“仍然找对”一起评估。
另一个实际坑是删除和更新。文档内容更新后,旧分块向量不能只依赖数据库行覆盖就视为语义一致。应使用文档版本号或内容哈希,确保检索结果不会混入旧版本;删除时还要确认异步 Embedding 队列不会把旧数据重新写回来。
七、学习与落地路径
建议按以下顺序学习,而不是先背索引参数:
-
先掌握距离函数和 Embedding 契约
用同一批文本比较余弦距离、内积和欧氏距离,理解归一化的影响。 -
建立精确搜索基线
使用 PostgreSQL 17 和 pgvector 建表,加入租户、状态、时间过滤,记录查询计划和质量指标。 -
再学习 HNSW 与 IVFFlat
分别改变m、ef_construction、ef_search或聚类参数,观察召回率、内存和延迟的变化。 -
补齐数据库工程能力
学习EXPLAIN (ANALYZE, BUFFERS)、分区、VACUUM、备份恢复、连接池和复制延迟。 -
最后接入 Go 1.24 与 Kubernetes Operator
实现异步向量化、幂等写入、模型版本管理、指标监控和故障恢复。
进阶资料应优先看 PostgreSQL 官方文档、pgvector 的 README 与源码、HNSW 原论文,以及目标 Operator 的版本兼容矩阵。AI 编程工具可以加速 SQL、Go 数据访问层和压测脚本的初稿,但不能替代召回率数据、执行计划和故障演练;这些部分必须用真实数据验证。
总结:PostgreSQL + pgvector 的价值,不只是把向量放进关系库,而是让语义检索与事务、权限和业务过滤处于同一数据边界。
先建立精确搜索基线,再选择 HNSW 或 IVFFlat;先验证过滤后的召回率,再谈索引参数。
你的业务中,向量检索最大的瓶颈是单纯的 ANN 性能,还是租户权限过滤导致的有效召回不足?