向量数据库与 HNSW:原理与选型

T2-4模块 2 · RAG 工程化面试权重 更新于 2026-08
前置T2-3
关联题目Q2-07Q2-08Q2-23

这篇学完你能回答什么

  • 「HNSW 是怎么做近似最近邻的?相比暴力检索牺牲了什么?」
  • 「Milvus / Qdrant / pgvector / Chroma 怎么选?」
  • 「知识库更新了,索引怎么增量更新?删除的文档怎么办?」

从一个真实故障讲起

两条典型的踩坑路径,你多半会遇到其中一条:

重装上阵:项目刚立项就上 Milvus 分布式集群,结果知识库才两万条文档,团队一半精力耗在维护 etcd、对象存储、消息队列这套依赖上,需求天天变,谁都不敢动索引结构。

轻装冒进:用 Chroma 跑了半年原型,数据涨到三百万条,某天线上检索延迟从 80ms 飙到 2 秒——单机 HNSW 索引内存放不下,扩容只能整库重建。

两种坑根子一样:选型时只看了「能不能跑起来」,没看「这条路能走多远」。

只问跑不跑得起来,不问走得多远
Chroma 跑了半年原型,数据涨到三百万条那天,检索延迟从 80ms 飙到 2 秒

  1. 用 Chroma 跑原型

    进程内嵌,运维极低,先跑起来再说

  2. 数据涨到三百万条

    选型时只看了「能不能跑起来」

  3. 单机索引放不下断点

    HNSW 图结构常驻内存,单机装不进

  4. 延迟 80ms → 2 秒

    扩容只能整库重建

线上检索延迟80ms2 秒
另一条坑 · 重装上阵知识库才两万条文档一半精力耗在 etcd、对象存储、消息队列
选型时问的问题「能不能跑起来」没问「这条路能走多远」
两条坑的根子是同一个。向量库的第一性问题从来不是哪个库最强,而是你的数据量、和团队愿不愿意长期养一套分布式系统 —— 两万条文档上 Milvus 集群,和三百万条还赖在 Chroma 上,是同一个错误的两个方向。

核心概念:向量库到底特殊在哪

普通数据库擅长精确匹配(WHERE id = 123)。向量检索要回答的是「跟这个向量最像的 10 个是谁」——在百万、亿级高维向量里做这件事,暴力算一遍全部距离在延迟上完全不可接受。

向量数据库 = 专为高维向量设计的存储与检索系统,核心能力是近似最近邻检索(ANN, Approximate Nearest Neighbor),配套元数据过滤、增删改和分布式扩展。

关键词是 Approximate(近似)用一点点召回率,换几个数量级的速度。 理解这个 trade-off 就理解了向量库的本质。

精确匹配那套,问不出「最像的 10 个」
普通数据库擅长对上主键就返回;向量检索要回答的是「跟这个向量最像的 10 个是谁」

WHERE id = 123

报得出主键就精确命中

换成「跟这个最像的 10 个是谁」,它答不了

对应
向量数据库

专为高维向量设计的存储与检索系统

核心能力是近似最近邻检索,外加元数据过滤、增删改、分布式扩展

元数据过滤增删改分布式扩展
挨个量一遍距离

百万、亿级高维向量逐条比

结果最准,但延迟上完全不可接受

对应
ANN · 近似最近邻

Approximate Nearest Neighbor

用一点点召回率,换几个数量级的速度

Approximate召回率让一点速度快几个数量级
题眼是 Approximate(近似):这笔交易是主动做的,不是模型不行。所以「召回不是 100%」在向量库里是设计选择而非 bug —— 理解这个 trade-off,后面 HNSW 的每个参数才有意义。

原理拆解:HNSW

上层跳得远,下层找得准
多层小世界图:从顶层入口贪心下沉,复杂度从暴力的 O(N) 降到大约 O(log N)

顶层 · 城市级节点只有北京、上海、广州这样的节点,你一步跳到上海上层稀疏,用来「长距离跳跃」
中层 · 上海各个区在这一层跳到黄浦区,范围收窄一圈每层贪心走向离目标更近的邻居,走不动了就下沉一层
底层 · 街道和店铺所有节点都在这一层,在附近小范围精找,输出 Top-K下层稠密,用来「精细定位」;每层的边连成一张小世界网络,任意两点跳几步就能到
三个旋钮(建好之后只剩一个还能动)
参数作用调大的后果改它要不要重建
M每个节点的邻居数(图的稠密度)召回↑、内存↑、构建慢要重建
ef_construction建索引时的候选队列长度索引质量↑、建索引更慢要重建
ef_search查询时的候选队列长度召回↑、查询变慢不用,运行时直接调

一句很实在的工程回答:「召回不够,先调 ef_search 看看」—— 它是唯一能在线调的旋钮,不重建索引就能试。而 Mef_construction 一旦建好就固定了。

其他索引:IVF 先聚类、只搜几个簇,内存友好但召回略低;DiskANN 把索引放 SSD,适合内存放不下的超大规模;量化(标量/乘积/二值)用精度换内存,能压到原来的几分之一。

代价落在三处:图结构常驻内存,涨到千万级时先顶不住的是内存(开篇那个故障就是它);召回不是 100%,ef_search 调太小会漏;删除只能软删,打标记跳过,删多了得重建。

HNSW(Hierarchical Navigable Small World,分层可导航小世界)是目前最主流的 ANN 索引,pgvector、Qdrant、Milvus 默认都用它。

直觉:像查地图一样找最近的餐馆

想象你要从北京找上海某条街的一家店:

  • 顶层:只有城市级节点(北京、上海、广州)。你一步跳到上海。
  • 中层:上海的各个区。你跳到黄浦区。
  • 底层:所有街道和店铺,你在附近小范围精找。

HNSW 就是这个结构——多层图,上层稀疏用来"长距离跳跃",下层稠密用来"精细定位"。每层的节点通过边连成一张"小世界"网络(任意两点间跳几步就能到),检索时从顶层入口出发,每层贪心地走向离目标更近的邻居,走不动了就下沉一层,直到底层输出 Top-K。

复杂度从暴力检索的 O(N) 降到大约 O(log N)——这就是它能撑起毫秒级检索的原因。

三个必须知道的参数

参数 作用 调大的后果
M 每个节点的邻居数(图的稠密度) 召回↑、内存↑、构建慢
ef_construction 建索引时的候选队列长度 索引质量↑、建索引更慢
ef_search 查询时的候选队列长度 召回↑、查询变慢(唯一能在线调的旋钮)

面试要点ef_search 是运行时参数,可以不重建索引直接调——「召回不够先调 ef_search 看看」是很实在的工程回答。而 Mef_construction 一旦建好就固定了,要改得重建。

HNSW 的代价(追问重点)

  • 内存开销大:图结构常驻内存,向量涨到千万级时内存可能成为瓶颈——上面「轻装冒进」那个故障就是这么来的。
  • 召回不是 100%:本质是近似,ef_search 调太小会漏。
  • 删除是软删除:图结构不好真删,一般打标记跳过,删多了需要重建(下面细讲)。

其他索引:IVF(先聚类再只搜几个簇,内存友好但召回略低)、DiskANN(索引放 SSD,适合超大规模内存放不下的场景)、量化(标量/乘积/二值量化,用精度换内存,能压到原来的几分之一)。知道它们的存在和适用场景即可。

工程实践(截至 2026-08)

选型对比

甜点区 运维负担 备注
pgvector 已有 PostgreSQL、百万级以内 极低(装个扩展) 事务一致性好,向量和业务数据同库 JOIN;性能不及专用库
Qdrant 中等规模、性能优先 低(单二进制/docker run) Rust 实现无 GC 停顿,元数据过滤强,单节点 P99 延迟通常最低
Milvus 亿级、真正的分布式 高(依赖 etcd/对象存储/消息队列) 大规模场景验证最充分;2.5+ 内置 BM25 全文检索,一库搞定混合检索
Weaviate 混合检索、开发体验 原生 hybrid 查询带 α 参数
Chroma 原型、快速验证 极低(进程内嵌) 不建议上生产:不支持混合检索、扩展性有限
Elasticsearch / OpenSearch 团队已有 ES 栈 BM25 底子厚,向量能力后补

一句话决策

原文示意
数据量 < 100 万 且 已有 PostgreSQL  → pgvector(零额外运维)
中等规模、要低延迟和强过滤          → Qdrant
亿级 / 要分布式 / 要一库做混合检索  → Milvus
只是原型                            → Chroma,但规划好迁移

好消息是迁移成本没想象中高:embedding 结果是确定性的,换库不需要重新计算向量,导出向量和元数据重新写入即可,百万级通常半小时内搞定。所以「先用简单的,顶不住再迁」是合理策略——但前提是别在 Chroma 上跑到三百万条才想起来。

几个容易被追问的工程点

元数据过滤:预过滤 vs 后过滤。 带条件的查询(category = 'X' 且语义相似)有两种执行顺序:先过滤再搜(预过滤)或先搜再过滤(后过滤)。后过滤有个致命问题:Top-100 里可能只有 3 条满足条件,结果不够用。Qdrant、Milvus 默认预过滤;pgvector 要留意查询计划。这题是区分「用过」和「读过」的好探针。

增量更新与删除(对应 Q2-23):

  • 新增:直接 upsert,HNSW 支持增量插入。
  • 更新:删旧 + 插新,注意向量和原文要原子性对齐,否则会出现「检索到了但取不到原文」。
  • 删除:多数实现是软删除(打标记跳过),空间不会立刻释放;删除比例高了会让图结构劣化、召回下降,需要定期 compact 或重建索引。
  • 实用做法:用版本号/时间戳做元数据,检索时过滤旧版本,再异步清理——避免更新期间出现检索空窗。

维度与存储估算:1536 维 float32 单条约 6KB,100 万条约 6GB(还没算图结构开销)。这个粗算能力面试时很好用——顺便可以带出降维(T2-3)和量化两个优化方向。

不是哪个库最强,是你养不养得起
六个库的甜点区各不重叠;迁移成本没想象中高,但别拖到三百万条才想起来

库选型(截至 2026-08)
甜点区运维负担与备注
pgvector已有 PostgreSQL、百万级以内极低(装个扩展)。事务一致性好,向量和业务数据同库 JOIN;性能不及专用库。零额外运维,起步默认
Qdrant中等规模、性能优先低(单二进制/docker run)。Rust 实现无 GC 停顿,元数据过滤强,单节点 P99 延迟通常最低
Milvus亿级、真正的分布式高(依赖 etcd/对象存储/消息队列)。大规模场景验证最充分;2.5+ 内置 BM25 全文检索,一库搞定混合检索
Weaviate混合检索、开发体验中。原生 hybrid 查询带 α 参数
Chroma原型、快速验证极低(进程内嵌)。不建议上生产:不支持混合检索、扩展性有限
Elasticsearch / OpenSearch团队已有 ES 栈中。BM25 底子厚,向量能力后补
决策线与容易被追问的点
一句话决策
< 100 万 且已有 PostgreSQL → pgvector;要低延迟强过滤 → Qdrant;亿级/分布式 → Milvus
只是原型
Chroma 可以,但规划好迁移 —— 别在它上面跑到三百万条才想起来
迁移没那么贵
embedding 是确定性的,换库不用重算向量;导出向量和元数据重新写入,百万级通常半小时内搞定
预过滤 vs 后过滤
后过滤的致命处:Top-100 里可能只有 3 条满足条件。Qdrant、Milvus 默认预过滤,pgvector 要留意查询计划
增量更新
新增直接 upsert;更新 = 删旧 + 插新,向量和原文要原子对齐,否则「检索到了但取不到原文」
存储粗算
1536 维 float32 单条约 6KB,100 万条约 6GB —— 还没算图结构开销
更新期间还有个坑叫检索空窗:删旧插新的过程里,那条内容一时查不到。实用做法是拿版本号/时间戳当元数据,检索时过滤旧版本,再异步清理。另外,预过滤 vs 后过滤这题是区分「用过」和「读过」的好探针。

面试视角

选型题,反问一句就分高下
追问链:怎么选库 → 为什么不用普通库 → HNSW 原理 → 参数怎么调 → 更新删除 → 涨十倍

  1. 类比开场HNSW = 多层地图跳跃,上层跳远、下层精找
  2. 点明本质权衡「近似」= 用一点点召回换几个数量级速度
  3. 给参数和实用细节三个参数,其中 ef_search 可在线调
  4. 选型先反问规模先问数据量和团队运维能力,再给结论
只读过:这些回答会暴露你
  • 上来就说「我用 Milvus」,不问数据量 —— 反倒暴露没做过选型
  • 只会讲 HNSW「快」,说不清怎么跳、为什么是 O(log N)
  • 把「召回不是 100%」当成 bug,近似索引本来就会漏
  • 答不出删除是软删除,也不知道删多了要 compact 或重建
  • 被问量级只能含糊过去,1536 维 100 万条要多少 G 算不出来
真做过:这些细节骗不了人
  • 先反问规模和运维人手再给结论,两万条文档不上分布式
  • 讲得出顶层跳跃、下层精找、走不动就下沉这条路径
  • 「召回不够先调 ef_search 看看」—— 运行时参数,不用重建索引
  • 主动说软删除会让图结构劣化、召回下降,要定期 compact
  • 张口就来:1536 维 float32 单条约 6KB,100 万条约 6GB
面试官在这题上要的是判断力,不是词汇量:能把「数据量 × 运维人手」和库名连起来的人,比背得出三个参数的人稀缺得多。配套题目:Q2-07Q2-08Q2-23

典型追问路径:向量库怎么选(Q2-08)→ 为什么不用普通数据库 → HNSW 原理(Q2-07)→ 参数怎么调、召回不够怎么办 → 数据更新删除怎么处理(Q2-23)→ 数据量涨十倍怎么办。

答题结构建议:HNSW 用「多层地图跳跃」的类比开场,然后点明「近似」这个本质权衡,再给三个参数和 ef_search 可在线调这个实用细节。选型题一定要先问规模和团队运维能力再给结论——上来就说「我用 Milvus」而不问数据量,反而暴露了没做过选型。

配套题目:Q2-07Q2-08Q2-23

小结与延伸

一句话总结:向量库的核心是用「近似」换速度,HNSW 靠多层小世界图把 O(N) 降到 O(log N);选型的第一性问题不是哪个库最强,而是你的数据量和团队愿不愿意长期运维一套分布式系统

延伸:HNSW 原论文(Malkov & Yashunin);各库官方的索引参数调优文档;下一篇 T2-6 讲 Rerank——检索召回之后,怎么把最相关的顶到最前面。

继续深入

本篇归属第 2 章「RAG 工程化」,去做这一章的题