向量数据库选型 2026:pgvector 与专用引擎的边界
向量库选型不是哪个库更好的榜单问题。本文把负载还原为规模维度、过滤选择性、召回形态三个正交维度,沿着 Postgres 内建路径(pgvector 的事务一致性与迭代扫描)与专用引擎路径(Qdrant 的原生过滤与 RRF/DBSF 融合、Milvus 的分布式分片)分别推演,给出过滤塌陷、多租户隔离、量化预算、embedding 迁移与离线评估的可执行判断标准。
约 29 分钟阅读8,481 字2 次阅读博主

向量库选型不是哪个库更好的榜单问题。本文把负载还原为规模维度、过滤选择性、召回形态三个正交维度,沿着 Postgres 内建路径(pgvector 的事务一致性与迭代扫描)与专用引擎路径(Qdrant 的原生过滤与 RRF/DBSF 融合、Milvus 的分布式分片)分别推演,给出过滤塌陷、多租户隔离、量化预算、embedding 迁移与离线评估的可执行判断标准。

过去两年,几乎每一份 AI 应用的技术选型文档里都会出现同一句话:"我们需要一个向量数据库。"这句话本身没有错,错的是它把一个工程决策压缩成了一个未定义的术语。当真正的实施者拿到这句话时,面临的实际问题是:向量规模在什么量级时,Postgres 里加个扩展就够了?什么时候必须引入一个独立的检索引擎?过滤条件与向量检索的耦合会不会让"最省事"的选择变成最贵的选择?
本文不做向量数据库的横评榜单,而是尝试还原这个问题真实的结构。方法是把"选型"拆解成三个正交维度——数据规模与维度、过滤选择性、召回形态——然后沿着这三个维度,分别走一遍 Postgres 内建路径与专用引擎路径,看清两者在什么地方分岔、在什么地方重新汇合。
文中所有版本号、星数与参数默认值均于 2026 年 10 月 29 日通过 GitHub API 与各项目 README 实时拉取核对,未采用记忆中的快照。
绝大多数向量库选型之所以变成口水战,是因为不同厂商在推销完全不同的负载画像。真正决定架构的往往只有三件事。
维度一:规模与向量维度。 决定性因素不是条数本身,而是"条数 × 维度"共同决定的内存足迹与索引构造成本。一个一千万条、768 维的稠密向量集合,按 float32 存储约 30 GB,若用 HNSW 并按常见经验预留索引内存开销,实际常驻内存往往是原始向量体积的两到三倍。这直接推导出两条分岔:如果单机能装下,嵌入式或单机进程内方案的最优解往往不是"数据库",而是"一个带向量索引的文件"或"一个库";如果装不下,才真正进入分布式检索引擎的讨论范畴。
维度二:过滤选择性。 这是最容易被低估、也最致命的一个维度。向量检索几乎从不裸跑——真实业务请求都带元数据条件:租户、文档类型、权限、时间窗、语言。"过滤选择性"指的是过滤后剩余的候选比例。这个比例决定了过滤发生在索引之前还是之后,而这又决定了召回率是否会塌陷(下一节会详细展开)。
维度三:召回形态。 是纯向量召回,还是稀疏关键词与稠密语义融合?是单路 top-k,还是需要多路融合、重排、引用溯源?召回形态直接映射到系统的能力边界:纯稠密召回是所有引擎的基线能力,而"原生混合检索 + 融合策略 + 分面聚合"往往是专用引擎比通用数据库多出来的那部分工程量。
把三个维度摆在一起,选型就不再是"哪个库更好",而是"我的负载落在哪个象限"。
pgvector 是这条路线的默认选项。截至 2026 年 10 月 29 日,其 GitHub 仓库星数为 23,523,最新标签版本为 v0.8.7,支持 Postgres 13 及以上版本。它提供精确最近邻搜索与近似搜索两类能力,距离度量覆盖 L2、内积、余弦、L1、Hamming 与 Jaccard。
pgvector 提供 HNSW 与 IVFFlat 两类近似索引,以及精确扫描(不加索引)作为召回率天花板。
HNSW 的核心参数是 hnsw.m(图的出度,默认 16)与 hnsw.ef_construction(构建期候选队列,默认 64),查询期则由 hnsw.ef_search 控制(默认 40)。工程直觉是:m 与 ef_construction 决定索引的"记忆质量",ef_search 决定单次查询愿意多走多少条边换召回。README 中明确提到,当过滤条件只命中 10% 的行、且沿用默认 hnsw.ef_search 为 40 时,平均只有 4 行能通过过滤——这个数字直接说明了过滤与近似索引的耦合问题。
IVFFlat 则用聚类中心划分空间,lists 参数决定聚类数(经验值约为表行数的平方根量级),ivfflat.probes 决定查询时探查的聚类数。探查越多召回越高、延迟越大,但对数据分布漂移更敏感。
从 v0.8.0 开始,pgvector 引入了迭代索引扫描(hnsw.iterative_scan 与 ivfflat.iterative_scan),取值包括 strict_order 与 relaxed_order。前者在扫描更多索引元组以凑够结果数的同时保持严格距离序;后者允许放松顺序以换取更低的扫描开销。配套的还有 hnsw.max_scan_tuples 与 ivfflat.max_probes 两个上限约束。README 给出的调优线索是:若增大 max_scan_tuples 不再改善召回,应改为调高 hnsw.ef_search。
这个特性是 pgvector 在"强过滤 + 高召回"场景下最关键的一块拼图,它把"过滤后候选不足"从一个需要应用层自己兜底(比如放大 top-k 再手工过滤)的坑,变成了数据库内部的原生能力。
pgvector 支持多种存储类型:halfvec 半精度浮点(最多 4,000 维)、sparsevec 稀疏向量(最多 1,000 个非零元素)、以及位向量用于 Hamming 与 Jaccard 距离。半精度把内存占用砍半,在多数重排模型之前先做粗召回的场景里,是性价比极高的一刀——粗排阶段本来就不需要精确距离。
halfvec 的索引创建需要写成 CREATE INDEX ON items USING hnsw ((embedding::halfvec(3)) halfvec_l2_ops),查询侧用 ORDER BY embedding::halfvec(3) <-> '[1,2,3]'——注意查询表达式必须与索引表达式一致,否则索引不会被使用。这是迁移时最常见的静默退化点。
它赢在事务与 JOIN:知识库产品几乎必然需要把向量和文档元数据、权限表、审计日志放在同一个事务边界内,这是 Postgres 不可替代的地方。ACID、点时间恢复、普通的 JOIN 能力,让整个"知识库"数据模型可以收敛到一个关系数据库里,而不用维护两套一致性。
它止步的地方也清晰:近似搜索是这个扩展实现而非内核原生,过滤策略是"扫描后过滤"的补正模型;多租户隔离需要靠 Postgres 原生的行级安全策略或分区表来实现;原生混合检索需要自己在 SQL 里把 BM25(用 Postgres 全文检索)与向量距离分数做手工融合。
Qdrant(2026 年 10 月 29 日星数 35,306,最新发布 v1.20.0,发布于 2026-10-22)把"过滤"做成了第一公民。官方 README 的表述很直接:Qdrant 专为扩展过滤支持而定制,适合神经或语义匹配、分面搜索等场景。它把向量与任意 JSON payload 一起存储,过滤支持关键字匹配、全文、数值范围、地理定位等条件,并可用 should/must/must_not 组合。
更关键的是两个融合原语:Reciprocal Rank Fusion(RRF)与 Distribution-Based Score Fusion(DBSF)。这意味着"稠密向量 + 稀疏关键词"的多路召回融合是引擎内置的一等能力,而不是应用层手写的排序逻辑。README 还提到查询规划会利用 payload 索引来优化执行策略,以及内建量化可把内存占用削减最多 97%,并提供分面聚合(faceting)能力。值得注意的是 Qdrant 官方还维护了一套面向 AI 编码助手的 agent skills,覆盖量化、分片、租户隔离、混合检索与模型迁移等决策场景——这本身说明该项目的重心正在从"存储"转向"应用决策支持"。
Milvus(星数 46,526,最新发布 v3.0.4,发布于 2026-10-26)代表的是另一条路线:分布式优先。它的 segment 与 partition key 机制面向的是需要水平切分、跨节点扩容的超大规模场景,以及需要物理隔离来保证租户数据不共置的高合规要求。
同一梯度上还有别的选项:Weaviate(星数 16,927,v1.40.3,发布于 2026-10-28)、Vespa(星数 7,153,v8.778.45,发布于 2026-10-27)、Chroma(星数 29,601,最新发布 1.5.9,发布于 2026-05-05)、LanceDB(星数 11,810,v0.41.0,发布于 2026-10-29)。其中 Chroma 的最新正式发布距今已近半年而仓库仍有 10 月的提交,提示使用者按实际提交节奏而非仅按发布标签评估活跃度;LanceDB 则代表嵌入式/文件化路线——对单机与边缘场景,它的形态更接近"一个目录"而非"一个服务"。
当应用同时需要语义召回与关键词精确匹配,工程上必须回答:两路分数量纲不同,如何合并?
最常见的答案是 RRF。它只依赖排名而非分数,因此对两路分数量纲不一致具有天然鲁棒性:
其中 是参与融合的候选列表集合, 是文档 在第 路列表中的排名, 是平滑常数(常用取值为 60)。分母里的 抑制了头部排名的过度主导:若 ,第 1 名得分为 1,第 2 名 0.5,头部效应极强; 时第 1 名约 0.0164、第 10 名约 0.0143,差异被显著压平。
DBSF 走的是另一条路:它基于分数分布而非排名做融合,先对每一路的分数做分布归一化,再按偏离程度赋权,理论上在两路分数分布存在系统性偏斜时表现更好。
在工程实现上,融合的正确姿势是在足够宽的候选池上做融合,而不是在最终 top-k 上做。常见错误是每路只取 top-20 就融合,导致两路都没有覆盖到真正相关的文档。实践中应让每路取到数百条,融合后再截断到 top-k,再交给重排模型。
回到第二层提出的过滤问题。设全库 条,过滤后剩 条,选择性 。
**前过滤(pre-filter)**先确定候选集合再做向量搜索,召回正确但代价高——当 极小时(比如 0.1%),搜索范围太小,HNSW 层的图剪枝优势无从发挥,退化为近似暴力扫描。
**后过滤(post-filter)**先做 ANN 取 top-k 再应用过滤,快但召回塌陷。若 且 ANN 取 k=40,期望通过过滤的只有 4 条,想拿 10 条就得把 k 提到 100——延迟随之线性上升,还得接受"某些查询根本拿不满"的风险。
迭代扫描是第三条路,也是 pgvector v0.8.0 引入的机制:让索引自己继续往下探,直到凑够满足过滤条件的结果数或触及扫描上限。它把"应用层放大 k 再过滤"的粗糙补救,替换成了"数据库保证过滤后仍有足够候选"的精确机制。
结论很直接:过滤选择性低于某个量级时,应当显式开启迭代扫描,而不是靠放大 top-k 兜底;而需要复杂分面聚合、需要内置混合融合、需要分片横向扩展的场景,则是专用引擎的领地。判断一个负载是否"重度依赖过滤",最直接的观察指标是:用户查询里有多少比例带了元数据条件。超过一半,就说明这个系统本质上是一个带语义层的检索系统,而不只是"向量数据库"。
多租户向量库不存在"正确答案",只有三种实现路径及其代价:
每租户一个 collection(或一张表)。 隔离最彻底,查询时无需携带租户过滤条件,天然规避选择性塌陷问题,物理删除也干净。但租户数量上万时,集合数量膨胀会带来元数据压力,冷租户的索引常驻内存造成浪费。
行级隔离 + 强制过滤。 所有数据在一张表,租户标识作为过滤条件强制下推到检索层。这与前述过滤问题同源:如果所有查询都带 tenant_id = X 且各租户数据量均衡,选择性天然良好;但单个大租户占据 80% 数据时,该租户的查询会遭遇后过滤塌陷。
分区表 / partition key。 Postgres 分区表提供物理隔离与分区裁剪的组合;Milvus 的 partition key 提供类似的按租户分片能力。这是介于前两者之间的折中:既避免了集合数量膨胀,又让查询能通过裁剪缩小扫描范围。
选型的现实约束是数据合规要求:若合同或法规要求租户数据不得共置同一物理节点,那么前两种方案不可用,只有物理分片一条路。
向量库选型中最常被误算的一笔账是内存。粗算方式是"条数 × 维度 × 4 字节 × 倍数",但这个粗算会同时高估和低估。
高估在于:实际部署中相当一部分向量可以用半精度(halfvec)或标量量化存储,原始体积直接减半;Qdrant README 宣称内建量化最多可削减 97% 的内存占用。
低估在于:ANN 索引本身的内存开销常常与原始向量同量级甚至更大。HNSW 的 m 参数越大图越稠密,ef_construction 越大构建期候选队列越大,两者都直接抬高索引内存;IVFFlat 虽省内存,但召回对 lists 与 probes 的敏感度更高,调优空间更窄。
一条务实的路径是分层精度:全量向量用半精度或量化存储承担粗排,只对粗排召回的 top-50~100 保留 float32 原始向量(或直接在粗排时就做量化还原)交给重排模型。粗排阶段召回 100 条准确率损失有限,重排阶段精度完全无损,内存却省下了一大截。
embedding 模型每半年就换代一次,维度、语义空间、归一化策略都会变。这带来一个绕不开的运维命题:同一张表里同时存在两代向量。
常见做法是加列:embedding_v1 与 embedding_v2 并存,新查询走新列,后台任务做回填(backfill),回填完成后切换读路径,最后在确认无回滚需求后删除旧列。这个流程里真正容易被忽略的两点:
第一是归一化语义的变化。余弦距离与内积在向量已归一化时等价,但很多模型在新版改变了是否归一化的默认约定。如果索引是 vector_l2_ops 而模型输出未归一化,相似度结果会静默失真。索引类型与模型输出约定必须在迁移 checklist 里配对检查。
第二是回填期间的召回质量断层。新列只回填了 60%,意味着 40% 的文档在新旧空间中不可比。如果此时把读路径切到新列,同一查询在不同时间会返回结构不同的结果,而监控往往捕捉不到这种漂移。
可观测性上建议埋一个简单的指标:新旧两列在同一批查询上的 top-k 重合率。它比任何离线评估都更早地暴露迁移断层。
向量库选型如果不可度量,就必然退化为信仰之争。最低限度的评估应覆盖三层:
召回层。 定义 :近似检索返回的 top-k 与精确检索(暴力计算)返回的 top-k 的重合比例:
注意分母用 而非 ,因为精确检索在高维空间的召回数量本身就可能不足 k,用它做分母会让指标大于 1。Recall@k 是纯粹关于索引质量(参数配置)与数据规模(建索引/查询成本)的指标,与业务质量无关——它衡量的是"索引是否忠实于暴力搜索",而不是"结果是否有用"。
业务层。 端到端指标,例如带 rerank 的最终 top-10 的人工相关性评分、答案采纳率。这才是最终要优化的东西。
运维层。 p50/p95/p99 延迟、索引构建耗时、内存驻留、写入吞吐、单位查询成本。
一个实用的评估方法论是按参数网格做离线扫描:在真实查询集与真实过滤条件集上,对 (索引类型, m, ef_construction, ef_search, lists, probes) 做网格搜索,画出"召回率 vs p95 延迟"的帕累托前沿,再在业务集上验证前沿上的候选点。这套方法在 pgvector 上尤其容易实现,因为所有参数都能在会话级 SET / SET LOCAL 直接调整,无需重建索引。
从"想换一个向量库"到"换成功",中间有几条常被低估的路径成本:
索引无法迁移,必须重建。 向量数据库的索引是与数据存储格式绑定的,没有一个跨引擎通用的索引导出格式。这意味着迁移是"导出数据 + 重新导入 + 重新建索引",成本随数据量线性增长,大集合可能需要数小时到数天的后台窗口。
查询语义不完全对等。 各引擎的距离度量、过滤表达式语法、融合实现都有细微差别。同一段查询逻辑换引擎后行为可能变化,这类问题在测试环境往往测不出来,直到线上某个长尾过滤条件触发。
分页语义差异。 ANN 检索下的 OFFSET 深分页在多数引擎里既慢又不稳定(第 500 页往后可能返回重复或缺失)。正确做法是游标式分页,但这需要应用层配合改造。
常见的失败模式清单:用 top-k 放大来掩盖过滤塌陷(掩盖了问题而非解决它);粗排候选池过小(每路只取 top-20 就融合);量化后未做重排精度回归;多租户方案上线后才迁移到物理隔离;以及最根本的一条——在没有离线评估集的情况下就做了选型决策。
把上面的分析压成一条路径:
第一步,算内存。 条数 × 维度 × 4 字节,乘上 1.5~3 倍的索引开销,对照机器可用内存。装得下,就不需要分布式引擎。
第二步,量过滤比例。 超过一半的查询带元数据条件,或者租户是主要隔离维度,说明这是"带语义层的检索系统"。此时优先保证过滤选择性可控(迭代扫描 / 分区 / 每租户 collection)。
第三步,定召回形态。 只要纯向量召回,Postgres + pgvector + 迭代扫描通常足够,且在事务一致性上有不可替代的优势。一旦需要原生混合检索(内置 RRF/DBSF)、分面聚合、跨节点分片,专用引擎的收益才真正兑现。
第四步,建评估集。 在此之前不要签任何不可逆的迁移工单。
最后补一句:LangChain(星数 148,682,langchain-core 最新发布 1.7.1,发布于 2026-10-28)与 LlamaIndex(星数 52,729,最新标签 v0.14.25)这类编排框架已经把向量库接入高度抽象化,换引擎的"代码层"成本在下降。但这也意味着向量库的差异正在被框架的抽象层吃掉——真正不可被抽象消解的,只剩三件事:过滤选择性下的召回保证、混合融合的实现质量、以及多租户隔离的物理形态。选择向量库,本质上就是选择这三件事由谁来兜底。
一句话摘要:向量库选型不是"哪个库更好",而是把负载还原成规模维度、过滤选择性、召回形态三个正交维度后,判断 Postgres 内建路径(pgvector 的事务一致性与迭代扫描)与专用引擎路径(Qdrant 的原生过滤与融合、Milvus 的分布式分片)各自的兑现条件——先用离线评估集锚定召回率与延迟帕累托前沿,再签字。
Conversation
0 条