选型地图 —— 别从「哪个最快」开始问
知识库要上线了,该选哪个向量数据库?2026 年的答案比两年前更分化:有的是「Postgres 里加个扩展」,有的是为十亿级设计的分布式系统,有的是跑在对象存储上的无服务器服务。
先说一个反直觉的结论:在大多数业务里,性能不是选型的第一变量。当规模在千万条以内,几个主流方案在延迟和召回上的差距,远小于「你的团队已经会运维什么」「你需要混合检索吗」「有多少租户」这些问题带来的差距。下面的向导把这几个变量问一遍,给出倾向,而不是标准答案。
几类方案的定位
| 方案 | 一句话定位 | 资料里的信号 |
|---|---|---|
| pgvector / pgvectorscale | Postgres 的扩展,向量与业务数据同库、同事务 | ACID 与关系数据联查是最大优势;多篇 2026 博客认为超过约 1000 万~5000 万条后运维开始吃力 博客观点 |
| Qdrant | Rust 实现的专用向量库,过滤与低延迟见长 | 多篇对比里单节点延迟领先 厂商/博客数据 |
| Milvus | 分布式、索引类型最全(IVF、HNSW、DiskANN、GPU 等),面向超大规模 | 索引构建快、可上 GPU;小规模也可用 Lite / standalone 博客观点 |
| LanceDB | 嵌入式、基于 Lance 列存格式,天然适合对象存储 | 适合本地原型、边缘与数据湖场景;支持 RaBitQ 量化 |
| turbopuffer | 建在对象存储上的无服务器检索,冷数据成本低 | 仅托管;冷启动查询延迟需实测 |
| Pinecone | 全托管,开箱即用 | 省运维,规模上去后成本需评估;闭源 |
| Elasticsearch / OpenSearch | 已有搜索集群时顺手加向量 | 原生 BM25 + 向量混合;内存与调优成本需评估 |
查看参考答案
先问:数据有多少、团队已经会运维什么、要不要关键词搜索加向量搜索一起用、有多少个客户要隔离。benchmark 像厂商自己出的考试成绩,题目、考场都是他们选的,只能当参考,要拿自己的数据去考一遍。
决定变量:规模量级、既有技术栈(如已有 Postgres)、混合检索需求、租户模型与过滤选择性、运维模式(托管/自运维/嵌入式)、冷热数据成本。厂商基准的数据集、召回目标、硬件与参数不一致,且常只报单一召回点上的 QPS,不可横向比较。
进阶架构 —— 不是每个问题都配得上同一条流水线
上线后会发现:「退款几天」这种问题一次检索就够,「对比三份合同里的违约条款」一次检索根本不够,而「今年工单的整体趋势」几乎不可能靠检索几个块回答。一条固定的流水线,对三类问题是同一个答案:都不太行。
2026 年的主流做法叫 Adaptive RAG(自适应):前面放一个轻量的路由,先判断问题属于哪一类,再走对应的流水线。检索也从「固定前置步骤」变成可被调用的工具,这就是 Agentic RAG:模型自己决定查什么、查几次、查完是否够用。选哪条路径,直接决定了你要付出几次模型调用和多少延迟。
GraphRAG:什么时候值得为「关系」付钱
有些问题的答案不在任何一个块里,而在块与块的关系里:「A 事故和 B 变更有什么关联」「所有提到供应商 X 的合同里,付款条款有何共性」。GraphRAG 的做法是在建库时让模型抽取实体和关系,构成知识图谱,再在图上做检索与摘要。代价主要在建库:据 2026 年一篇实践博客的对比,对 500 页语料建索引,LightRAG 约 3 分钟、$0.5,微软 GraphRAG 约 $50~200(博客数据,量级参考)。
- 适合:知识天然互相引用(制度引用制度、事故关联变更)、需要多跳推理或全局主题归纳。
- 不适合:扁平的 FAQ 与文档问答,普通混合检索 + 重排已经够用,上图是为复杂度付费。
- 起步顺序:先把混合检索做好,评估后确有「关系类问题答不好」再引入,从 LightRAG 这类轻量方案试起。
查看参考答案
简单问题直接查一次;一句话含几件事,拆开分别查;要一层层追问的,让模型边查边想;要「总体上怎么样」的,靠图谱或预先做好的摘要。越复杂的路径,模型调用越多,越慢越贵,所以要先分诊再开药。
简单事实 → 单次混合检索 + 重排;多意图 → 问题分解 + 多路检索 + RRF;多跳 → Agentic 迭代检索(带轮数/预算上限);全局归纳 → GraphRAG 或层级摘要(map-reduce)。Agentic 的代价是 LLM 调用数与延迟的放大,以及对失控循环的治理需求。
上线的四件事 —— 权限、新鲜度、溯源与成本账
原型能跑和生产能用之间,隔着四件和检索算法无关、却最容易出事故的事。
用户不该看到的文档,一旦进了提示词,模型就可能复述出来。正确做法是检索时就带上用户身份的过滤条件(租户、部门、文档密级),连候选都不让它进来。别指望「让模型别说」。
块上存 doc_id、version、updated_at;文档更新时按 doc_id 删旧块再写新块(不是追加)。更换嵌入模型要整库重嵌入,所以要保留原文与块版本,别只存向量。
提示词里给每块编号,要求模型引用编号;回答里把编号映射回文档链接和段落。这既是用户信任的来源,也是你排查错误答案时的第一手线索。
每次提问最贵的是把检索到的块送进大模型。先优化 top-k、块大小、是否真需要重排,往往比换一个更便宜的向量库省得多。下面的计算器让你亲手核一遍。
查看参考答案
权限像图书馆门禁:不让不该看的人进书库,比让他进去再叮嘱「别乱说」可靠得多。成本方面,把计算器的默认值跑一遍,会发现大模型读取那些块的费用远超向量库的内存费用,所以优先瘦身 top-k 和块大小。
权限:将 ACL 作为检索过滤条件(元数据过滤),保证未授权内容不进入上下文。成本:LLM 输入 token = top-k × 块大小 + 固定开销,随 QPS 线性增长;向量库成本主要是内存(向量 + 索引)。优化顺序:降低 top-k/块大小、重排后截断、缓存高频问题,再考虑量化与换库。
实战 A · pgvector —— 在 Postgres 里搭一套混合检索
把前面三页串起来:同一个客服知识库,用 Postgres + pgvector 做到「向量 + 关键词 + RRF 融合 + 租户过滤」。已经有 Postgres 的团队,这是成本最低的路径。
1. 起一个带 pgvector 的 Postgres
docker run -d --name kb-pg -p 5432:5432 \ -e POSTGRES_PASSWORD=pw pgvector/pgvector:pg17
2. 建表与索引
CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE chunks ( id bigserial PRIMARY KEY, tenant_id text NOT NULL, doc_id text NOT NULL, version int NOT NULL DEFAULT 1, content text NOT NULL, -- 原文,给模型和用户看 content_seg text NOT NULL, -- 应用层分词后用空格连接(中文必需,见下方说明) embedding vector(1024) NOT NULL, tsv tsvector GENERATED ALWAYS AS (to_tsvector('simple', content_seg)) STORED ); -- 向量索引:m / ef_construction 为 pgvector 默认值,建库后不再改 CREATE INDEX chunks_hnsw ON chunks USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); CREATE INDEX chunks_tsv ON chunks USING gin (tsv); CREATE INDEX chunks_tenant ON chunks (tenant_id);
simple 配置只按空格和标点分词,不认识中文词界,直接对中文原文建 tsvector 基本等于没有。三条路:① 应用层先用 jieba 之类分词,存空格连接的 content_seg(本例,适合托管 Postgres,不用装插件);② 装 zhparser、pg_jieba 等分词插件;③ 用 ParadeDB 等提供真 BM25 的扩展。另外,ts_rank_cd 不是 BM25(没有语料级的 IDF),只是够用的排序函数。3. 写入与混合检索(Python)
import jieba, numpy as np, psycopg from pgvector.psycopg import register_vector conn = psycopg.connect("postgresql://postgres:pw@localhost:5432/postgres", autocommit=True) register_vector(conn) # 让 numpy 数组可直接作为 vector 参数 seg = lambda s: " ".join(jieba.lcut(s)) def upsert_doc(tenant, doc_id, version, chunks, embed): # 更新 = 先删旧版本的块,再写新块(不要只追加) conn.execute("DELETE FROM chunks WHERE tenant_id=%s AND doc_id=%s", (tenant, doc_id)) for text in chunks: conn.execute( "INSERT INTO chunks (tenant_id, doc_id, version, content, content_seg, embedding) VALUES (%s,%s,%s,%s,%s,%s)", (tenant, doc_id, version, text, seg(text), np.array(embed(text)))) HYBRID_SQL = """ WITH vec AS ( SELECT id, row_number() OVER (ORDER BY embedding <=> %(qv)s) AS rnk FROM chunks WHERE tenant_id = %(tenant)s ORDER BY embedding <=> %(qv)s LIMIT 20 ), lex AS ( SELECT id, row_number() OVER (ORDER BY ts_rank_cd(tsv, q) DESC) AS rnk FROM chunks, to_tsquery('simple', %(qs)s) q WHERE tenant_id = %(tenant)s AND tsv @@ q ORDER BY ts_rank_cd(tsv, q) DESC LIMIT 20 ) SELECT c.id, c.doc_id, c.content, COALESCE(1.0 / (60 + vec.rnk), 0) + COALESCE(1.0 / (60 + lex.rnk), 0) AS rrf FROM vec FULL OUTER JOIN lex USING (id) JOIN chunks c USING (id) ORDER BY rrf DESC LIMIT 5 """ def search(tenant, question, embed): terms = [t for t in jieba.lcut(question) if t.strip() and t.isalnum()] qs = " | ".join(terms) or "''" # 关键词路用 OR 连接,提升召回 conn.execute("SET hnsw.ef_search = 100") # 查询期调召回/延迟 conn.execute("SET hnsw.iterative_scan = strict_order") # 带过滤时自动继续扫索引(pgvector 0.8+) return conn.execute(HYBRID_SQL, {"qv": np.array(embed(question)), "qs": qs, "tenant": tenant}).fetchall()
4. 验证它真的在用索引
EXPLAIN (ANALYZE, BUFFERS) SELECT id FROM chunks WHERE tenant_id = 'acme' ORDER BY embedding <=> '[0.1, 0.2, ...]' LIMIT 20; -- 期望看到:Index Scan using chunks_hnsw ... -- 如果是 Seq Scan:检查向量维度、距离算子是否与索引的 vector_cosine_ops 一致(<=>)
三个常见的踩坑点:① 距离算子要和索引的操作类对应(余弦用 vector_cosine_ops 配 <=>),不一致时索引不会被使用,不报错只变慢;② 带 WHERE 过滤时,HNSW 可能先扫出 ef_search 条再过滤导致不足,pgvector 0.8 的迭代扫描就是为此(strict_order 保证距离顺序,relaxed_order 更快但可能轻微乱序);③ 大批量导入时,先导数据再建 HNSW 索引通常比边插边建快得多。
查看参考答案
SQL 里 vec 那一段是按语义找,lex 那一段是按关键词找,最后 SELECT 里把两边的名次换算成分数相加就是 RRF。Postgres 自带的全文检索看不懂中文怎么断词,所以要先在代码里切好词再存。
vec CTE:HNSW 近邻(cosine)取 top-20 并用 row_number 记名次;lex CTE:GIN tsvector 匹配 + ts_rank_cd 取 top-20;外层 FULL OUTER JOIN 后按 Σ 1/(60+rank) 融合。simple 配置不做中文分词,故在应用层分词写入 content_seg,查询侧同样分词并以 OR 组合。带租户过滤用 hnsw.iterative_scan 避免过滤后结果不足。
实战 B · Milvus —— 用专用向量库做同一件事
同一个任务,换成 Milvus:它把「全文检索」做成了内置的 BM25 函数,混合检索是一等公民,不用自己写 SQL 拼融合。
1. 启动 Milvus(Docker 单机版)
curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
bash standalone_embed.sh start # 默认监听 19530
pip install -U pymilvus也可以先用本地文件版的 Milvus Lite(MilvusClient("./kb.db"))做原型;全文检索、混合检索等较新的能力是否在 Lite 里可用,请以当前版本文档为准,下面按 standalone 写。
2. 建集合:稠密向量 + BM25 稀疏向量 + 租户分区键
from pymilvus import (MilvusClient, DataType, Function, FunctionType, AnnSearchRequest, RRFRanker) client = MilvusClient(uri="http://localhost:19530") schema = client.create_schema(auto_id=True) schema.add_field("id", DataType.INT64, is_primary=True) schema.add_field("tenant_id", DataType.VARCHAR, max_length=64, is_partition_key=True) # 多租户:分区键 schema.add_field("doc_id", DataType.VARCHAR, max_length=128) schema.add_field("text", DataType.VARCHAR, max_length=8192, enable_analyzer=True, analyzer_params={"type": "chinese"}) # 中文分析器;英文用 "standard",以官方文档为准 schema.add_field("dense", DataType.FLOAT_VECTOR, dim=1024) schema.add_field("sparse", DataType.SPARSE_FLOAT_VECTOR) # 由 BM25 函数自动生成 schema.add_function(Function(name="text_bm25", function_type=FunctionType.BM25, input_field_names=["text"], output_field_names=["sparse"])) index = client.prepare_index_params() index.add_index(field_name="dense", index_type="HNSW", metric_type="COSINE", params={"M": 16, "efConstruction": 200}) index.add_index(field_name="sparse", index_type="SPARSE_INVERTED_INDEX", metric_type="BM25") client.create_collection("kb", schema=schema, index_params=index)
3. 写入与混合检索
def upsert_doc(tenant, doc_id, chunks, embed): client.delete("kb", filter=f'tenant_id == "{tenant}" and doc_id == "{doc_id}"') # 先删旧块 client.insert("kb", [{"tenant_id": tenant, "doc_id": doc_id, "text": t, "dense": embed(t)} for t in chunks]) # 注意:不用自己算 sparse,BM25 函数会根据 text 自动生成 def search(tenant, question, embed): flt = f'tenant_id == "{tenant}"' dense_req = AnnSearchRequest(data=[embed(question)], anns_field="dense", param={"metric_type": "COSINE", "params": {"ef": 100}}, limit=20, expr=flt) sparse_req = AnnSearchRequest(data=[question], anns_field="sparse", # 直接传查询文本 param={"metric_type": "BM25"}, limit=20, expr=flt) res = client.hybrid_search("kb", reqs=[dense_req, sparse_req], ranker=RRFRanker(60), limit=5, output_fields=["doc_id", "text"]) return [(h["entity"]["doc_id"], h["entity"]["text"], h["distance"]) for h in res[0]]
Function 与 RRFRanker 承担,代码短得多,代价是多运维一个组件、多学一套概念。注意示例里的 filter 字符串拼接仅为演示,生产环境请对租户 ID 做校验或使用参数化方式,防止表达式注入。查看参考答案
BM25 由 schema 里那个 Function 自动把文本变成稀疏向量;RRF 由 hybrid_search 里的 RRFRanker(60) 完成;租户隔离靠 tenant_id 的 is_partition_key 加上每次检索带的 filter。
schema.add_function(BM25) 生成 sparse 字段;SPARSE_INVERTED_INDEX + metric BM25 建索引;两路 AnnSearchRequest(dense / sparse)经 hybrid_search + RRFRanker 融合;is_partition_key=True 按租户哈希分区,expr 过滤只扫相关分区。
对照与排障 —— 两套怎么选,答案错了怎么查
两套方案都能把客服知识库跑起来。差别不在「能不能」,而在你愿意把复杂度放在哪。
| 维度 | pgvector | Milvus |
|---|---|---|
| 起步成本 | 已有 Postgres 时几乎为零 | 多一个服务(standalone)或一个集群 |
| 关键词路 | tsvector + 自己分词,非 BM25 | 内置 BM25 函数与分析器 |
| 融合 | 自己写 SQL(RRF 十几行) | RRFRanker 一行 |
| 事务与联查 | 与业务表同库同事务,可直接 JOIN | 需在应用层关联业务数据 |
| 规模上限 | 博客多认为千万到数千万级后吃力 | 为亿级到十亿级设计,索引类型丰富 |
| 多租户 | 字段过滤 + 迭代扫描;可用分区表 | partition key 原生支持 |
| 运维 | 沿用现有 DBA 体系(备份、监控、权限) | 需学习新组件;也可用托管版 |
建议路径:已有 Postgres 且预计千万级以内,从 pgvector 起步,把精力放在切块、混合检索、评估上;等到规模、过滤复杂度或写入吞吐真的顶到天花板,再迁移,因为你保留了原文与块版本,迁移只是重新嵌入加导入,不是推倒重来。(这也是「架构决策往长了做」的体现:数据结构设计对,换引擎不疼。)
答案不对:一棵排障决策树
用户反馈「答错了」,别急着换模型。按下面的顺序一层层排除,每个叶子都指向前面对应的章节。
查看参考答案
先看正确内容有没有在库里,再看有没有被找出来、有没有排进送给模型的前几名,最后才看模型有没有照着资料回答。每一层有不同的药:补文档、改检索、调排序、收紧提示词。
终极测验 · 5 题验收
答对 4 题以上,你就能带着这一整套去落地了。
参考资料 · References
产品能力与代码接口会随版本更新,本页代码核对于 2026-10-05,使用前请对照官方文档。博客类来源标为「观点」。
官方文档(代码出处)
- pgvector README(HNSW 参数、
hnsw.ef_search、迭代扫描、混合检索)· github.com/pgvector/pgvector - pgvector-python 混合检索示例(RRF)· examples/hybrid_search/rrf.py
- Milvus · Full Text Search(BM25 Function)· milvus.io/docs/full-text-search.md
- Milvus · Multi-Vector Hybrid Search(AnnSearchRequest、RRFRanker)· milvus.io/docs/multi-vector-search.md
- Milvus · Use Partition Key(多租户)· milvus.io/docs/use-partition-key.md
2026 年的资料
- Best Vector Databases in 2026: Pricing, Scale Limits, and Architecture Tradeoffs Across Nine Leading Systems(MarkTechPost,2026-05)· marktechpost.com
- Choosing the Foundation for Your RAG System: pgvector vs Qdrant vs Milvus (2026)(观点)· dev.to
- Best Vector Databases in 2026: A Complete Comparison Guide(观点)· firecrawl.dev
- Vector Database Benchmark 2026(厂商数据,注意口径)· salttechno.ai
- GraphRAG in 2026: What to Use, When to Use It, and What to Watch Out For(成本与适用场景,观点)· medium.com
- Graph RAG in 2026: What Works in Production(观点)· paperclipped.de
- RAG Best Practices 2026: Chunking, Reranking, Hybrid Search(Adaptive RAG、RRF k=60,观点)· callmissed.com
- Is RAG Dead? Long Context, Grep, and the End of the Mandatory Vector DB(2026-04,观点)· akitaonrails.com
选型向导的打分规则与成本计算器的所有单价,都是本页脚本里可读可改的假设,不构成对任何产品的评测结论。