两大近似最近邻(ANN)索引,目的都是为了加速向量检索、不搞全表扫描,但它们的底层算法、性能侧重点、以及对硬件资源(内存/CPU)的消耗有着本质的区别。
把 document_chunk 表中的几百万条向量比作“全国各地的居民”。现在要找“跟张三长得最像的 10 个人”。
核心原理的区别
IVFFLAT(倒排文件扁平索引)—— “划分行政区”
IVFFLAT 的核心思想是聚类(Clustering)。它在创建索引时,会使用 K-Means 算法把所有向量划分成 N 个“桶”(也就是你代码里指定的 lists = 100)。
- 建立索引时: 算法在全国选出 100 个城市中心(聚类中心),然后把每个居民(向量)划分到离他最近的那个城市桶里。
- 查询时(
SET ivfflat.probes = 10): 当你带着目标向量来查询时,系统先看这个向量离哪 10 个城市中心(probes)最近。然后只进这 10 个城市桶里进行逐一比对,剩下的 90 个桶直接看都不看。
HNSW(分层导航小世界网络)—— “建立朋友圈网络”
HNSW 的核心思想是图结构(Graph)。它把每一个向量都变成图里的一个“节点”,彼此相近的向量之间会连上一条线,而且这个图是分层(Layered)的。
- 建立索引时: 顶层网络很稀疏(类似全国高铁网,只有大城市),越往下层越密(类似省内公路网、市区街道网)。
- 查询时: 采用“激进搜索”。先在最顶层大跨步地跳跃,快速定位到目标大概在哪个“省”;然后降到下一层,在更密的网络里精细定位;最后降到最底层,顺着连线找到离你最近的邻居。
关键参数的区别
结合你给出的代码,我们来看看参数控制了什么:
IVFFLAT 参数
lists = 100: 决定了把数据切分成多少个聚类桶。数据量大时(如百万级),这个值通常要设得更大(例如 $\sqrt{\text{行数}}$)。ivfflat.probes = 10: 决定了查询时同时扫描多少个桶。这个值越大,查得越准,但速度越慢。如果设成100(等于lists),就退化成了全表扫描。
HNSW 参数
m = 16: 决定了每个向量节点在构建图时,最多和多少个邻居连线。m越大,图越稠密,准确率越高,但索引体积暴增。ef_construction = 64: 决定了建索引时,为了寻找邻居需要评估多少个候选节点。这个值只影响建索引的速度和质量,值越大建索引越慢,但索引质量越好。- (补充)
hnsw.ef_search: 查询时的控制参数(类似 ivfflat 的 probes)。值越大,查询越准,速度越慢。
企业落地时的核心对比表
| 对比维度 | IVFFLAT | HNSW (推荐) |
|---|---|---|
| 查询速度 (Latency) | 中等。数据量极大时速度会变慢。 | 极快。高并发下依然能保持毫秒级响应。 |
| 查询准确率 (Recall) | 较低。因为有些边界向量可能会被错误地划分到隔壁桶,导致漏报。 | 极高。图网络的泛化和逼近能力极强。 |
| 内存消耗 (RAM) | 极小。基本不怎么占内存,对硬件非常友好。 | 极大。它需要把整个高维图结构常驻内存。内存不够会导致频繁磁盘 I/O,性能雪崩。 |
| 构建/更新索引速度 | 快。建索引耗时短,新增数据时速度还行。 | 极慢。建索引非常吃 CPU。而且数据频繁更新(INSERT/UPDATE)时,图结构要频繁拆线重连,代价极高。 |
企业中该如何选择?
在目前的工业界 RAG 落地中,HNSW 已经成为了绝对的主流方案。
- 选 HNSW 的场景(90% 的选择): 如果你的业务是线上的实时客服、搜索推荐。用户对响应时间(Latency)要求极高(要求 < 50ms),且要求回答足够精准,同时公司预算充足,服务器内存管够(比如 64G/128G 内存起步)。
- 选 IVFFLAT 的场景: 如果你是在做后台离线分析、批处理,或者公司处于创业初期硬件资源非常紧张,服务器只有可怜的 4G/8G 内存,哪怕查询慢一点、准确率低一点,只要系统不把内存挤爆(OOM)就行。