PostgreSQL 17向量检索工程选型与边界

2 阅读7分钟

如果你的数据、权限和业务过滤条件都在 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 引入了迭代扫描相关能力,用于在过滤后继续扩展搜索范围,但它不能替代合理的数据分布设计。

例如,以下两种设计的成本不同:

  1. 一个共享表,所有租户混在一起,再用 tenant_id 过滤;
  2. 按租户或业务域进行分区,使向量搜索只在相关分区中发生。

分区不是默认答案。租户数量极多且数据量差异明显时,分区管理、索引数量和查询规划都会变复杂。应通过实际过滤选择性、数据规模和租户增长曲线决定。

四、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 服务应将“向量生成”和“向量检索”拆成两个可独立观测的阶段:

  1. 文档变更进入队列;
  2. Worker 调用 Embedding 服务;
  3. Worker 写入向量及模型元数据;
  4. 查询服务生成问题向量;
  5. PostgreSQL 执行带业务过滤的 ANN 查询;
  6. 可选地使用重排模型对前几十条候选重新排序。

写入时不要把向量拼接进 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 队列不会把旧数据重新写回来。

七、学习与落地路径

建议按以下顺序学习,而不是先背索引参数:

  1. 先掌握距离函数和 Embedding 契约
    用同一批文本比较余弦距离、内积和欧氏距离,理解归一化的影响。

  2. 建立精确搜索基线
    使用 PostgreSQL 17 和 pgvector 建表,加入租户、状态、时间过滤,记录查询计划和质量指标。

  3. 再学习 HNSW 与 IVFFlat
    分别改变 m、ef_construction、ef_search 或聚类参数,观察召回率、内存和延迟的变化。

  4. 补齐数据库工程能力
    学习 EXPLAIN (ANALYZE, BUFFERS)、分区、VACUUM、备份恢复、连接池和复制延迟。

  5. 最后接入 Go 1.24 与 Kubernetes Operator
    实现异步向量化、幂等写入、模型版本管理、指标监控和故障恢复。

进阶资料应优先看 PostgreSQL 官方文档、pgvector 的 README 与源码、HNSW 原论文,以及目标 Operator 的版本兼容矩阵。AI 编程工具可以加速 SQL、Go 数据访问层和压测脚本的初稿,但不能替代召回率数据、执行计划和故障演练;这些部分必须用真实数据验证。

总结:PostgreSQL + pgvector 的价值,不只是把向量放进关系库,而是让语义检索与事务、权限和业务过滤处于同一数据边界。
先建立精确搜索基线,再选择 HNSW 或 IVFFlat;先验证过滤后的召回率,再谈索引参数。

你的业务中,向量检索最大的瓶颈是单纯的 ANN 性能,还是租户权限过滤导致的有效召回不足?