向量数据库(01):向量索引与检索——Embedding、相似度、HNSW/IVF、召回与性能权衡
更新时间:2026-09-02。本文是
data-ai/vector-db/第 01 篇,在向量数据库索引下。向量数据库是 RAG 的底座:把文本变成向量,再按"语义相近"快速找回来。本文讲清楚"相似怎么算、为什么需要索引、HNSW/IVF 怎么在速度和准确间做交易"。
本文要回答的问题
- 文本怎么变成向量?embedding 是什么?
- "语义相似"用什么度量?余弦、点积、欧氏距离怎么选?
- 为什么不能暴力算所有距离?ANN 近似最近邻牺牲了什么?
- IVF 和 HNSW 两大索引的原理是什么?参数怎么调?
- 召回率、延迟、内存三者怎么权衡?
一、Embedding:把语义装进向量
Embedding 模型把一段文本映射成一个固定长度的浮点数向量(如 768 维、1536 维),语义相近的文本,向量在空间中距离也近。
"如何重置密码" -> [0.12, -0.88, 0.33, ..., 0.05] (1536维)
"密码忘了怎么办" -> [0.15, -0.85, 0.31, ..., 0.07] ^ 距离很近(语义同)
"今天天气真好" -> [-0.7, 0.2, 0.9, ..., -0.4] x 距离很远(语义异)要点:
- Embedding 模型是专门训练来编码语义的(如 BGE、text-embedding-3、E5),不是随便拿 LLM 隐藏层。
- 查询和文档必须用同一个 embedding 模型,否则向量空间不一致,检索全乱。
- 中文场景选多语言/中文优化模型(BGE-large-zh、M3E 等)效果明显更好。
二、相似度度量
给定向量 q(查询)和一批文档向量 d,怎么算"谁最像"?
| 度量 | 公式直觉 | 何时用 |
|---|---|---|
| 余弦相似度 | 两向量夹角余弦,只看方向不看长度 | 文本语义检索默认,最常用 |
| 点积(内积) | 方向 + 模长都算 | 向量已归一化时等价于余弦;部分模型要求 |
| 欧氏距离 L2 | 空间直线距离 | 图像、坐标类;越小越相似 |
余弦相似度:
q · d
cos = ----------- 范围 [-1, 1],越接近 1 越相似
|q| |d|
文本 embedding 通常先归一化(模长=1),此时 余弦 == 点积。工程提示:很多向量库要求写入时选定距离类型(COSINE / IP / L2),必须和 embedding 模型的推荐用法一致;归一化后用内积最快。
三、为什么需要 ANN:暴力检索的墙
朴素做法是"精确最近邻"(KNN):查询 q 和库里每一个向量算一次距离,取 Top-K。
库规模 N=100万,维度 1536:
每次查询 = 100万 × 1536 次乘加 ≈ 15 亿次浮点运算
单次查询几十~上百毫秒,且随 N 线性增长 -> 上千万向量直接不可用于是引入 ANN(Approximate Nearest Neighbor,近似最近邻):用索引结构牺牲一点点召回率,换取数量级的提速。核心是"不必看全部向量,大概率看一小部分就能找到足够近的"。
精确 KNN: 看全部 N 个 召回 100% 慢
ANN: 只看 ~1% 候选 召回 95%+ 快 100~1000 倍"召回率"在这里指:ANN 找出的 Top-K 里,有多少和暴力 KNN 的真实 Top-K 重合。RAG 场景 95%~98% 召回通常就够用。
四、两大主流索引
1. IVF(Inverted File,倒排聚类索引)
思路:先把所有向量用 K-Means 聚成 nlist 个簇(Voronoi 格),查询时只搜离 q 最近的 nprobe 个簇。
训练阶段:K-Means 把空间切成 nlist 个格子,每格一个中心
查询阶段:
q -> 找最近的 nprobe 个中心 -> 只在这几格内算距离 -> Top-K
nprobe 小:快,但可能漏掉隔壁格的近邻(召回低)
nprobe 大:准,但慢(趋近暴力)- 参数:
nlist(簇数,常取 sqrt(N)~4*sqrt(N))、nprobe(查询探测簇数,速度/召回旋钮)。 - 常配合 PQ(乘积量化) 压缩向量:把向量切段、每段用码本近似,内存可降 8~32 倍,代价是精度损失。IVF-PQ 适合超大规模、内存敏感。
2. HNSW(Hierarchical Navigable Small World,分层可导航小世界图)
当前效果与口碑最好的 ANN 索引。构建一张多层图:
上层:节点稀疏、长边多,负责"快速跳跃"定位大致区域
o---------o---------o
\ / \ /
...(逐层加密,边变短)...
下层:节点稠密、短边多,负责"精细逼近"真正近邻
o-o-o-o-o-o-o-o-o-o-o
查询:从最高层入口贪心走向更近的节点,
逐层下降,底层做精细搜索 -> Top-K"小世界"特性:任意两点间都有一条很短的路径(六度分隔),所以即使亿级节点,也能跳 ~log(N) 步到达近邻。
关键参数:
M:每个节点的连边数。越大召回越高、内存越大、构建越慢(常 16~48)。efConstruction:建图时的搜索宽度,越大建图质量越高、越慢(常 200~500)。efSearch(查询时):查询搜索宽度,运行时调召回/延迟的主旋钮,越大越准越慢(常 50~200,且应 ≥ K)。
HNSW 召回高、查询快,缺点是内存占用大(要存图)、构建较慢、增量删除较麻烦。绝大多数 RAG 场景默认选 HNSW。
3. 选型直觉
| 索引 | 召回 | 延迟 | 内存 | 适合 |
|---|---|---|---|---|
| 暴力 KNN | 100% | 高 | 低 | 小数据(<10万)、要精确 |
| HNSW | 很高 | 很低 | 高 | RAG 默认,千万级、低延迟 |
| IVF | 中高 | 低 | 中 | 大规模批量 |
| IVF-PQ | 中 | 低 | 很低 | 亿级、内存吃紧、可容忍精度损失 |
五、召回率与性能的权衡旋钮
向量检索永远在 召回率 ↔ 延迟 ↔ 内存 三角里权衡:
高召回
/\
/ \
低延迟 /____\ 低内存
三者不可兼得,调参数就是在选边站实战调优清单:
- 先保证 embedding 质量:模型选对、查询/文档同一模型、中文用中文模型。索引再好也救不回差 embedding。
- Top-K 别太小也别盲目大:RAG 常取 K=5~20;K 太小漏证据,太大引入噪声且贵。
- 召回不够先调
efSearch/nprobe:这是运行期最便宜的旋钮,调大观察召回与延迟。 - 加混合检索:纯向量检索对"专有名词、型号、错误码"不敏感,配合 BM25 关键词检索做稠密+稀疏融合(hybrid search),再用重排(rerank)精排,效果显著提升(见 RAG 架构)。
- 量召回要靠评测集:构造"(查询, 期望命中文档)"标注集,对比 ANN 与暴力 KNN 算 recall@K,别凭感觉。
六、过滤与元数据
真实检索几乎都带条件:"只在产品 A 的文档里搜""只搜最近一年"。
- 预过滤(pre-filter):先按元数据(标量字段)筛,再在结果集上做向量检索。选择性高时快。
- 后过滤(post-filter):先 ANN 取一批,再过滤。可能过滤后不足 K 条,需要多取。
不同向量库对"标量过滤 + 向量检索"的优化程度差异很大(Milvus/Qdrant 支持较好,pgvector 依赖 SQL where)。设计 schema 时要把需要过滤的字段(租户、时间、类别)作为元数据一起存。
小结
向量检索的链路是:文本经 embedding 模型变成向量 → 用余弦/点积衡量语义相似 → 用 ANN 索引(HNSW 为主、IVF-PQ 应对超大规模)在召回率和速度间权衡 → 配合元数据过滤与混合检索/重排提升效果。记住三条:查询和文档必须同一 embedding 模型;召回不够先拧 efSearch/nprobe;专有名词与精确匹配要靠关键词检索补。下一篇讲具体选型与落地:向量数据库选型与落地。