RAG 向量数据库选型:先看查询模式

0阅读13分钟

不要先问哪个库最火

去年我给一个企业内部知识库做 RAG 落地,团队第一句话就是「用 Pinecone 还是 Milvus?」。我没接这个话,而是花了两周把真实查询日志拉出来分析——结果发现 70% 的查询都带着租户隔离、文档类型、时间范围这类过滤条件,而且有近 30% 的查询需要精确匹配产品编号。

这个发现直接否决了当时团队最倾向的方案。纯向量相似度检索根本扛不住这种查询模式。

向量数据库选型不该从工具开始,而该从查询模式开始。文档量、更新频率、过滤条件组合方式、权限模型和延迟预算,这些变量共同决定了你该走哪条路。热门工具如果不匹配你的查询模式,上线第一天就会出问题。

查询模式决定技术路径

在讨论任何具体产品之前,需要先理解向量检索的底层机制。RAG 系统里的向量检索本质是近似最近邻(Approximate Nearest Neighbor, ANN)搜索——在高维空间中找到与查询向量最接近的 K 个向量,但不保证找到的是绝对最近的那几个。

这个「近似」不是偷工减料,而是一个工程权衡:精确最近邻搜索的时间复杂度是 O(N·d),其中 N 是向量数量、d 是维度。当 N 达到千万级,暴力搜索在延迟上就不可接受了。ANN 算法通过预建索引结构,把查询复杂度压缩到 O(log N) 甚至更低,代价是召回率从 100% 降到 95%-99%。

两种主流索引结构

当前生产环境中最常见的两种 ANN 索引是 HNSW 和 IVF,它们的底层数据结构完全不同,适用的场景也不同。

HNSW(Hierarchical Navigable Small World) 基于图结构。它把向量组织成多层图:上层稀疏,用于快速跳跃到大致区域;下层密集,用于精细定位。查询时从顶层开始逐层向下遍历,每层走几步就跳到下一层。这种结构的优势是查询路径短、召回率高(通常 95% 以上),劣势是整个图必须常驻内存,内存占用与数据量线性相关。

HNSW 的核心调优参数有三个:

  • m(节点连接度):每个节点最多连接多少邻居。越大召回率越高,但内存和构建时间也越大。常用值 16-64。
  • ef_construction(构建期搜索宽度):建索引时每个节点探索多少候选。越大图质量越好,构建越慢。常用值 128-512。
  • ef_search(运行时探测深度):查询时探索多少候选。越大召回率越高,延迟越大。常用值 64-256。

IVF(Inverted File Index) 基于倒排结构。它先用 K-Means 把所有向量聚类到 nlist 个桶中,查询时只搜索距离查询向量最近的 nprobe 个桶。这种结构的优势是内存可控(可以结合量化压缩),劣势是需要预先训练聚类中心,数据分布漂移后可能需要重建索引。

IVF 的关键参数是两个:

  • nlist:聚类桶数量。经验值通常是 4·√N16·√N,N 是向量总数。
  • nprobe:查询时搜索的桶数。越大召回率越高,延迟越大。

过滤条件如何影响索引选择

上述两种索引在「无过滤」场景下表现都很好。问题出在加上过滤条件之后。

企业 RAG 几乎都需要过滤:按租户隔离数据、按文档类型缩小范围、按时间窗口截取。2026 年初的一篇 arXiv 论文(Filtered Approximate Nearest Neighbor Search in Vector Databases)系统分析了过滤对 ANN 检索的影响,结论是:

  • Pre-filtering(先过滤再搜索):用位掩码筛出符合条件的向量,然后在子集上做 ANN。当过滤条件很严格(比如只命中 1% 的数据)时,图结构会变得稀疏,HNSW 的召回率会急剧下降。
  • Post-filtering(先搜索再过滤):先做 ANN 找到 top-K,然后过滤掉不符合条件的。风险是过滤后结果可能远少于 K 个,甚至为空。
  • Runtime filtering(运行时过滤):图遍历过程中每访问一个节点就检查条件。延迟会增加(随机内存访问),但能保持较好的召回率。

Milvus 在 2.x 版本中实现了自适应策略——当符合条件的向量比例低于某个阈值时,自动从图遍历切换到精确扫描。Qdrant 则通过 payload 索引实现「零性能损耗」的过滤:对常用过滤字段建立独立的倒排索引,在图遍历前预先剪枝。

这不是纯理论问题。我在实际项目中就踩过这个坑。

案例一:租户过滤导致数据泄露

场景

一个 SaaS 平台为多个企业客户提供知识库问答。每个客户的文档通过 tenant_id 隔离。初期用 pgvector,查询逻辑是先做向量检索 top-20,再在应用层用 tenant_id 过滤。

翻车

上线两周后,安全审计发现 A 客户的问答结果中偶尔出现了 B 客户的文档片段。原因很直接:向量检索返回的 top-20 里只有 2-3 条属于当前租户,其余被过滤掉了,但应用层没有检查结果数量是否足够,直接把过滤后的少量结果喂给了 LLM。

更严重的情况是:当某个租户的文档量很少时,向量检索的 top-20 可能全部来自其他租户,过滤后结果为空,系统返回「未找到相关信息」——但知识库中明明有答案。

修复

把过滤条件下沉到数据库层,在向量检索阶段就完成租户隔离。pgvector 从 0.5 开始支持 HNSW 索引与普通 WHERE 条件结合,但需要注意过滤选择性和索引策略的配合。

# ❌ 错误做法:应用层过滤,存在数据泄露风险
def rag_query_bad(query_embedding, tenant_id):
    # 先做向量检索,返回全局 top-20
    results = db.execute("""
        SELECT content, tenant_id
        FROM document_chunks
        ORDER BY embedding <=> %s
        LIMIT 20
    """, (query_embedding,))
    
    # 应用层过滤——结果可能不足,甚至为空
    filtered = [r for r in results if r.tenant_id == tenant_id]
    return build_context(filtered)
# ✅ 正确做法:数据库层过滤,向量检索和权限检查同时生效
def rag_query_correct(query_embedding, tenant_id):
    # 过滤条件在 SQL 层执行,向量索引在过滤后的子集上搜索
    results = db.execute("""
        SELECT content
        FROM document_chunks
        WHERE tenant_id = %s
        ORDER BY embedding <=> %s
        LIMIT 20
    """, (tenant_id, query_embedding))
    return build_context(results)

这两个版本的差异看起来只是 WHERE 子句的位置不同,但在 pgvector 中这涉及索引策略的选择。当使用 HNSW 索引时,pgvector 会在索引遍历过程中并行评估过滤条件(hybrid filter),但前提是过滤字段上也需要有合适的索引支持。如果 tenant_id 上没有 B-tree 索引,pgvector 可能退化为全表扫描。

-- 必须同时为过滤字段和向量列建立索引
CREATE INDEX idx_tenant ON document_chunks (tenant_id);
CREATE INDEX idx_embedding ON document_chunks
    USING hnsw (embedding vector_cosine_ops)
    WITH (m = 16, ef_construction = 200);

案例二:纯向量检索漏掉精确匹配

场景

一个制造业客户的知识库包含大量产品手册,每份手册有唯一编号(如「MH-2024-0371」)。工程师提问时经常直接引用编号,比如「帮我查 MH-2024-0371 的维护流程」。

翻车

使用纯向量检索后,这类查询的召回率很低。原因是 embedding 模型会把编号编码成语义向量,但不同编号的向量在语义空间中可能非常接近(它们描述的都是类似类型的设备手册)。查询「MH-2024-0371」时,检索返回的可能是「MH-2024-0369」和「MH-2024-0372」的文档——语义相似但编号不对。

工程师连续几次拿到错误文档后,整个团队对 RAG 系统失去信任。

修复

引入混合检索:同时使用 BM25 关键词检索和向量语义检索,然后通过 RRF(Reciprocal Rank Fusion)合并结果。BM25 擅长精确匹配编号、术语和专有名词,向量检索擅长语义理解,两者互补。

# ❌ 错误做法:纯向量检索,无法处理精确匹配需求
def search_vector_only(query, collection):
    query_embedding = embed(query)
    results = collection.search(
        query_embedding,
        limit=10,
        # 只有语义相似度,编号「0371」和「0369」几乎无法区分
    )
    return results
# ✅ 正确做法:混合检索 + RRF 融合
from rank_bm25 import BM25Okapi
 
def search_hybrid(query, collection, bm25_index):
    # 向量检索:捕获语义意图
    query_embedding = embed(query)
    vector_results = collection.search(
        query_embedding,
        limit=20,
    )
    
    # BM25 检索:捕获精确关键词(编号、术语)
    tokens = tokenize(query)
    bm25_results = bm25_index.get_top_n(tokens, n=20)
    
    # RRF 融合:k 值通常取 60
    rrf_scores = {}
    k = 60
    for rank, doc in enumerate(vector_results):
        rrf_scores[doc.id] = rrf_scores.get(doc.id, 0) + 1.0 / (k + rank + 1)
    for rank, doc in enumerate(bm25_results):
        rrf_scores[doc.id] = rrf_scores.get(doc.id, 0) + 1.0 / (k + rank + 1)
    
    # 按 RRF 分数排序返回
    sorted_ids = sorted(rrf_scores, key=rrf_scores.get, reverse=True)
    return sorted_ids[:10]

Weaviate 和 Qdrant 都原生支持混合检索。Weaviate 的 GraphQL 接口可以在一个查询中同时执行 BM25 和向量检索并自动融合。Qdrant 在 2024 年推出了 BM42 混合检索框架,将 BM25 和稀疏向量检索统一到同一个 API 中。Milvus 通过分别创建稠密向量和稀疏向量字段来实现混合检索,灵活度更高但需要更多手动编排。

向量数据库选型矩阵

回到选型的核心问题。下面从多个维度对比当前主流的五个方案。

部署模式与扩展性对比

维度pgvectorMilvusQdrantWeaviatePinecone
部署方式PostgreSQL 扩展自托管 / K8s 原生自托管 / 云托管自托管 / 云托管纯 SaaS
开源协议PostgreSQL LicenseApache 2.0 (CNCF)Apache 2.0Apache 2.0闭源
数据规模上限~5000 万十亿级十亿级十亿级无明确上限
扩展方式单机(读副本可分担)分布式分片分片 + 副本分片 + 副本自动弹性伸缩
内存模型依赖 PostgreSQL 共享内存可配置(DiskANN 降内存)支持量化压缩需要较大内存平台管理
运维复杂度低(已有 PG 基础设施)高(需要 K8s + etcd + MinIO)中(单二进制部署)中(JVM 调优)极低(全托管)

检索能力对比

维度pgvectorMilvusQdrantWeaviatePinecone
索引类型HNSW / IVFFlatHNSW / IVF / DiskANN / AnnoyHNSW(量化可选)HNSWHNSW
距离度量L2 / 内积 / 余弦L2 / IP / 余弦 / 汉明 / 杰卡德余弦 / 欧氏 / 曼哈顿余弦 / 欧氏 / 点积余弦 / 欧氏 / 点积
过滤策略SQL WHERE(与索引并行)前置 / 自适应切换Payload 索引 + 运行时过滤原生过滤 + GraphQL元数据过滤
混合检索PG 全文检索 + 向量稠密 + 稀疏向量BM42 框架原生 BM25 + 向量2024 年引入,持续完善中
多向量支持不支持支持(多字段)支持有限支持不支持

索引算法适用规模对比

数据规模推荐索引预期召回率内存占用参考典型延迟
<100 万FLAT 或 HNSW100% / 97%+低 / 中<5ms / <3ms
100 万-1000 万HNSW 或 IVF_FLAT95-98%中-高 / 中<5ms / <10ms
1000 万-1 亿IVF_SQ8 或 HNSW(量化)93-97%中 / 中<10ms / <8ms
>1 亿DiskANN 或 IVF_PQ92-96%低 / 低<15ms / <20ms

运维成本对比

维度pgvectorMilvusQdrantWeaviatePinecone
初始部署一条 CREATE EXTENSION需要 K8s 集群单二进制 / DockerDocker Compose无需部署
备份恢复pg_dump 标准流程需配置 S3/MinIO快照 API快照 + 备份插件平台自动
索引重建REINDEX需要触发重建任务自动增量更新自动增量更新平台管理
监控告警PG 生态工具Prometheus + GrafanaPrometheus 端点Prometheus 端点控制台仪表盘
月成本参考(中等规模)0(复用现有 PG)服务器费用 ¥3000-8000服务器费用 ¥2000-5000服务器费用 ¥3000-6000$100-1000+

案例三:索引参数没调,延迟翻了 10 倍

场景

一个客服知识库,文档量约 200 万条 chunk,使用 Milvus + HNSW 索引。开发环境测试时延迟在 5ms 以内,上线后 P99 延迟飙到 50ms 以上。

翻车

排查后发现,开发环境的测试数据是随机生成的向量,分布均匀,HNSW 的图结构质量很好。生产数据是从真实文档生成的 embedding,存在明显的热点区域(大量关于退换货政策的 chunk 语义高度相似,向量挤在一起)。这导致 HNSW 图在热点区域的遍历路径变长,查询延迟上升。

另一个问题是 ef_search 参数设为了默认值 64,在生产数据的分布下不够用——需要探索更多节点才能找到足够近的邻居。

修复

调整了 HNSW 参数,并在数据入库后执行了一次全量索引优化。同时引入了量化压缩,将内存占用降低了约 40%。

# ❌ 错误做法:使用默认参数,不做针对性调优
from pymilvus import Collection, Index
 
collection = Collection("customer_service_chunks")
index_params = {
    "index_type": "HNSW",
    "metric_type": "COSINE",
    # 默认参数:m=16, ef_construction=200
    # 生产数据有热点分布,默认参数图质量不够
    "params": {}  # 没有设置任何参数
}
collection.create_index("embedding", index_params)
# ✅ 正确做法:根据数据分布和延迟预算调参
from pymilvus import Collection, Index
 
collection = Collection("customer_service_chunks")
index_params = {
    "index_type": "HNSW",
    "metric_type": "COSINE",
    "params": {
        "M": 32,               # 连接度从 16 提高到 32,图更密,热点区域遍历更快
        "efConstruction": 400,  # 构建时探索更充分,图质量更高
        "efSearch": 128,        # 查询时探索更多节点,召回率从 ~93% 提升到 ~97%
    }
}
collection.create_index("embedding", index_params)
 
# 同时设置搜索参数
collection.load()
collection.search(
    data=[query_embedding],
    anns_field="embedding",
    param={"metric_type": "COSINE", "params": {"ef": 128}},
    limit=10,
)

参数调优不是一次性工作。我后来建立了一个简单的基准测试流程:每次 embedding 模型更换或数据量增长超过 20%,就跑一轮 recall@10 和 P99 延迟测试。如果 recall 低于 95% 或 P99 超过 20ms,就调整 ef_search;如果调整 ef_search 后延迟仍然不达标,就考虑换索引类型(比如从 HNSW 切到 IVF_SQ8)。

选型决策流程

把上面的分析综合起来,选型可以按这个流程走:

流程图画布 · 115%
Mermaid 流程图加载中...

这个流程没有唯一正确答案,但它把关键决策点显式化了。每个菱形节点都是一个需要你根据实际业务判断的分叉口。

距离度量:不是余弦一定最好

选型时另一个容易忽略的决策是距离度量。很多人默认用余弦相似度,但这不一定是最优选择。

距离度量公式特点适用场景注意事项
余弦相似度只看方向,忽略长度文本语义匹配(文档长度不一)如果 embedding 已归一化,余弦等价于内积
L2(欧氏距离)同时考虑方向和长度图像特征、归一化后的向量对向量长度敏感,长文档和短文档可能不公平
内积(点积)方向 + 长度的综合已归一化的 embedding计算速度最快,但要求向量必须归一化
汉明距离二进制位差异二值化向量(极端压缩场景)精度损失大,只适合对精度要求不高的场景

OpenAI 的 text-embedding-3-small/largetext-embedding-ada-002 输出的向量已经归一化,此时余弦相似度和内积的排序结果完全一致,但内积的计算速度更快。如果你的 embedding 模型没有归一化输出,用余弦会更安全。

一个常见错误是在索引声明和查询时使用了不同的距离度量。比如建索引时声明用 L2,查询时按余弦排序——这会导致索引失效,退化为全量扫描,延迟可能翻 10 倍以上。

# ❌ 错误做法:索引和查询的距离度量不一致
# 建索引时用 L2
collection.create_index("embedding", {
    "index_type": "HNSW",
    "metric_type": "L2",  # 索引按 L2 构建
})
 
# 查询时按余弦排序——索引失效,退化为暴力搜索
results = collection.search(
    data=[query_embedding],
    anns_field="embedding",
    param={"metric_type": "COSINE", "params": {"ef": 128}},  # 度量不匹配!
    limit=10,
)
# ✅ 正确做法:索引和查询使用相同的距离度量
METRIC = "COSINE"
 
collection.create_index("embedding", {
    "index_type": "HNSW",
    "metric_type": METRIC,
    "params": {"M": 32, "efConstruction": 400},
})
 
results = collection.search(
    data=[query_embedding],
    anns_field="embedding",
    param={"metric_type": METRIC, "params": {"ef": 128}},  # 一致
    limit=10,
)

上线前的选型检查清单

以下是我在实际项目中总结的检查清单,按阶段分组。每一项都来自踩过的坑。

阶段一:需求分析

  • 拉取至少一个月的真实查询日志(如果还没有系统,用业务方提供的 50 个典型问题)
  • 统计查询中携带过滤条件的比例,以及过滤字段的种类和组合方式
  • 确认是否有精确匹配需求(编号、术语、专有名词),如果有,混合检索是必选项
  • 确认数据规模(当前 + 未来 12 个月预估),明确是百万级、千万级还是亿级
  • 确认数据更新频率:每天新增/删除/更新的文档量
  • 确认权限模型:是否需要行级权限(按租户、部门、角色)

阶段二:技术验证

  • 用真实数据(不是随机生成的向量)测试候选方案,随机数据测不出热点分布问题
  • 测试带过滤条件的召回率,不只是无过滤的 recall@10——过滤后召回率下降 5-10% 是常见的
  • 测试 P99 延迟而不是平均延迟,长尾延迟才是用户体验的瓶颈
  • 验证距离度量一致性:索引声明、查询参数、embedding 模型输出三者必须匹配
  • 如果有混合检索需求,测试 BM25 + 向量的 RRF 融合效果,不能只看单路召回

阶段三:运维准备

  • 确认索引重建的操作流程和耗时——embedding 模型更换或数据分布变化时可能需要重建
  • 确认备份恢复策略,向量数据库的备份通常比普通数据库大很多
  • 设置监控告警:索引大小增长速率、查询延迟 P99、召回率(通过定期抽样验证)
  • 计算完整成本:不只是数据库本身,还有 embedding API 调用、索引构建的计算资源、运维人力

几个容易踩的坑

不要迷信 benchmark 排行榜。 ann-benchmarks.com 上的排名是在特定数据集和特定过滤条件下测出来的。你的数据分布、查询模式和过滤条件和 benchmark 不一样,排名可能完全不同。2026 年初 YDB 团队专门发了一篇文章质疑公开 ANN benchmark 的可信度,核心观点是:很多数据库在 benchmark 上用了最优配置,但在生产环境中不会这么配。

不要忽略 embedding 模型对选型的影响。 不同的 embedding 模型输出的向量维度不同(768、1024、1536、3072),维度越高内存占用越大,对索引策略的要求也不同。如果你计划未来更换 embedding 模型,选型时就要考虑索引重建的成本。

不要把向量数据库当成银弹。 RAG 系统的质量瓶颈可能在 chunk 策略、embedding 模型质量或 reranker 效果上,而不是向量检索本身。我见过不少团队花大量时间优化向量检索参数,最后发现换一个 reranker 模型带来的提升更大。

pgvector 的适用边界比很多人想的要宽。 如果你的数据量在 500 万以下,过滤条件不复杂,而且已经在用 PostgreSQL,pgvector 大概率是成本最低、运维最简单的选择。不需要一上来就引入 Milvus 或 Pinecone。pgvector 0.7+ 的 HNSW 实现已经相当成熟,配合 pgvectorscale 的 StreamingDiskANN 可以扩展到更大规模。

参考资料

  1. Johnson, J., Douze, M., & Jégou, H. (2019). "Billion-scale similarity search with GPUs." IEEE Transactions on Big Data. Faiss 论文,IVF 索引的经典实现。https://arxiv.org/abs/1702.08734

  2. Malkov, Y. A., & Yashunin, D. A. (2018). "Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs." IEEE Transactions on Pattern Analysis and Machine Intelligence. HNSW 算法的原始论文。https://arxiv.org/abs/1603.09320

  3. Chen, Z. et al. (2026). "Filtered Approximate Nearest Neighbor Search in Vector Databases." arXiv preprint. 系统分析了过滤条件对 ANN 检索的影响,提出 MoReVec 基准。https://arxiv.org/html/2602.11443

  4. Milvus 官方文档. "Vector Index." 覆盖 HNSW、IVF、DiskANN 等索引类型的参数说明和选型指南。https://milvus.io/docs/index.md

  5. Qdrant 官方文档. "Hybrid Search with BM42." BM42 混合检索框架的技术说明。https://qdrant.tech/documentation/concepts/hybrid-search/

  6. Zilliz 技术博客. "RAG 搭建中,如何选择最合适的向量索引?" HNSW / IVF / DiskANN 在不同数据规模下的选型建议。https://zilliz.com.cn/blog/How-to-choose-the-most-suitable-vector-index-in-RAG-construction

  7. Weaviate 官方文档. "Hybrid Search." 原生 BM25 + 向量混合检索的实现说明。https://weaviate.io/developers/weaviate/search/hybrid

  8. ann-benchmarks.com. 向量检索算法的标准评测框架,提供可复现的 benchmark 结果。https://ann-benchmarks.com/

评论 0

0 / 1000