向量数据库索引机制与选型总结:从 FLAT、IVF 到 HNSW、DiskANN
向量数据库索引机制与选型总结:从 FLAT、IVF 到 HNSW、DiskANN
面向 Java 后端程序员的向量索引学习笔记。
本文重点解释:FAISS、Milvus、Schema、FLAT、IVF_FLAT、IVF_SQ8、HNSW、DiskANN 的内部结构、优缺点来源、是否需要训练、什么时候需要重建索引,以及如何做选型。
目录
- 从 MySQL / Redis 视角理解向量数据库
- FAISS 是什么:它是数据库吗
- FAISS 本地保存的 index.faiss 和 index.pkl
- Milvus 中的 Schema 概念
- 向量索引和 MySQL B+Tree 索引的本质区别
- FLAT:全量扫描索引
- IVF_FLAT:聚类中心 + 倒排桶
- IVF_SQ8:IVF + 向量压缩
- HNSW:多层近邻图索引
- DiskANN:面向超大规模的 SSD 图索引
- 哪些索引需要训练,哪些不需要
- IVF_FLAT 训练耗时与影响因素
- 新增数据后是否需要重建索引
- 各类向量索引优缺点总表
- 实战选型建议
- 问题追踪:你的追问路径与对应答案
- 最终记忆口诀
1. 从 MySQL / Redis 视角理解向量数据库
对于 Java 后端程序员来说,可以先用 MySQL 和 Redis 类比理解向量数据库。
1.1 MySQL 查什么?
MySQL 主要查结构化字段。
例如:
SELECT * FROM user WHERE id = 1001;
它找的是:
id 精确等于 1001 的那一行。
再比如:
SELECT * FROM user WHERE age > 18;
它找的是:
age 大于 18 的所有行。
MySQL 的 B+Tree 索引适合处理:
精确匹配
范围查询
排序查询
1.2 Redis 查什么?
Redis 更像高性能 KV 存储。
例如:
GET user:1001
它通过 key 快速找到 value。
Redis 查的是:
key -> value
1.3 向量数据库查什么?
向量数据库查的不是:
id 是否等于某个值
key 是否存在
而是:
谁和 query 向量最相似?
比如在 RAG 里,用户问:
RAG 的工作流程是什么?
系统会把问题转成向量:
query -> query_vector
然后在文档向量库里找:
和 query_vector 最相似的 topK 个文档 chunk。
所以向量数据库的核心查询是:
相似度搜索 / 最近邻搜索 / TopK 检索
1.4 三者对比
| 系统 | 查询对象 | 查询方式 | 索引目标 |
|---|---|---|---|
| MySQL | 结构化字段 | 精确匹配 / 范围查询 | 快速定位满足条件的行 |
| Redis | Key | key-value 查询 | 快速通过 key 找 value |
| 向量数据库 | Embedding 向量 | 相似度查询 | 快速找到最近邻向量 |
一句话理解:
MySQL 找“等于谁”。
Redis 找“key 对应谁”。
向量数据库找“最像谁”。
2. FAISS 是什么:它是数据库吗
2.1 FAISS 的定位
FAISS 全称一般理解为:
Facebook AI Similarity Search
它是 Meta/Facebook AI 开源的一个高性能向量相似度搜索库。
它主要解决的问题是:
给你大量向量,如何快速找到和某个 query 向量最相似的几个向量。
2.2 FAISS 是数据库吗?
严格来说:
FAISS 不是传统意义上的数据库。
它不像 MySQL、PostgreSQL、MongoDB 那样提供:
SQL 查询
事务
权限控制
网络服务
复杂字段过滤
分布式集群
备份恢复
FAISS 更准确的定位是:
本地向量检索库 / 向量索引库 / 向量搜索引擎。
2.3 为什么在 LangChain 里感觉 FAISS 像数据库?
因为 LangChain 把 FAISS 包装成了一个 VectorStore。
例如:
vectorstore = FAISS.from_documents(docs, embeddings)
这行代码内部会:
所以从使用体验上看,它像一个“本地向量数据库”。
但严格说:
FAISS 是向量检索库,不是完整数据库。
2.4 FAISS 和 Milvus 的区别
| 对比项 | FAISS | Milvus |
|---|---|---|
| 类型 | 向量检索库 | 向量数据库 |
| 是否独立服务 | 通常不是 | 是 |
| 是否有 Schema | 没有传统强 Schema | 有 Collection Schema |
| 是否支持元数据过滤 | 原生较弱,依赖外层封装 | 支持较完善 |
| 是否适合生产服务化 | 需要自己封装 | 更适合生产化 |
| 典型用途 | 本地实验、小型向量检索 | 企业级向量检索服务 |
可以这样记:
FAISS 更像一个高性能“向量搜索发动机”。
Milvus 更像一个完整的“向量数据库系统”。
3. FAISS 本地保存的 index.faiss 和 index.pkl
使用 LangChain 保存 FAISS 索引时,通常会生成两个文件:
faiss_index_store/
index.faiss
index.pkl
这两个文件负责不同的事情。
3.1 index.faiss 是什么?
index.faiss 是 FAISS 真正的向量索引文件。
它主要保存:
文档向量
向量索引结构
相似度搜索所需的数据结构
你可以把它理解为:
index.faiss = 向量检索引擎的数据文件
它负责:
根据 query 向量快速找到最相似的 vector_id。
但是它通常不直接保存完整原文。
3.2 index.pkl 是什么?
index.pkl 是 LangChain 保存的 Python pickle 文件。
它通常保存:
docstore:原始 Document 文档内容
index_to_docstore_id:FAISS 向量编号到 Document id 的映射
metadata:文档元数据
可以理解成:
index.pkl = 向量 id 和原始文档之间的映射表
3.3 两个文件如何配合?
假设你有三条文本:
vector 0 -> "张三是法外狂徒"
vector 1 -> "FAISS是一个用于高效相似性搜索和密集向量聚类的库。"
vector 2 -> "LangChain是一个用于开发由语言模型驱动的应用程序的框架。"
查询时:
用户问题:FAISS 是做什么的?
流程是:
1. embedding 模型把 query 转成 query_vector
2. index.faiss 找到最相似的向量编号,比如 vector_id = 1
3. index.pkl 根据 vector_id = 1 找回原始 Document
4. 返回文本:
"FAISS是一个用于高效相似性搜索和密集向量聚类的库。"
所以:
index.faiss 负责“找向量”
index.pkl 负责“找回原文”
3.4 为什么加载时需要 allow_dangerous_deserialization=True?
LangChain 加载本地 FAISS 时可能要反序列化 index.pkl。
pickle 文件有安全风险。如果加载的是恶意 pickle,理论上可能执行危险代码。
所以 LangChain 需要你显式声明:
allow_dangerous_deserialization=True
含义是:
我确认这个索引文件是可信的,可以反序列化。
注意:
只加载自己生成的 index.pkl,不要随便加载陌生来源的 pkl 文件。
4. Milvus 中的 Schema 概念
4.1 Schema 是什么?
在 Milvus 里,Schema 可以理解成 Collection 的“表结构设计图”。
类比 MySQL:
| MySQL | Milvus |
|---|---|
| Database | Database |
| Table | Collection |
| Column | Field |
| Row | Entity |
| Table Schema | Collection Schema |
| Index | Vector Index / Scalar Index |
所以:
Milvus 的 Schema 就是在创建 Collection 前,先定义这个 Collection 有哪些字段,每个字段是什么类型,哪个是主键,哪个是向量字段。
4.2 为什么 Milvus 需要 Schema?
因为 Milvus 不只是存向量,还会存元数据。
例如 RAG 中的一条 chunk 数据可能长这样:
{
"id": 1,
"chunk_text": "RAG 包含检索、增强和生成三个阶段。",
"embedding": [0.12, -0.35, 0.88, "..."],
"source": "rag_intro.md",
"page": 3,
"chapter": "RAG 工作流程"
}
Milvus 需要提前知道:
id 是 INT64
chunk_text 是 VARCHAR
embedding 是 FLOAT_VECTOR
source 是 VARCHAR
page 是 INT64
chapter 是 VARCHAR
这就是 Schema 的作用。
4.3 Milvus 中三类核心字段
1. 主键字段 Primary Key
用于唯一标识每条数据。
类似 MySQL:
id BIGINT PRIMARY KEY
在 Milvus 中:
id = 1001
表示唯一定位一条向量数据。
2. 向量字段 Vector Field
用于存 embedding 向量。
例如:
embedding: FLOAT_VECTOR, dim=512
dim=512 必须和 embedding 模型输出维度一致。
如果你的模型输出 512 维,但 Schema 写成 768 维,插入时会报错。
3. 标量字段 Scalar Field
标量字段就是普通字段,主要用于 metadata。
例如:
source
page
chapter
category
user_id
created_at
它们可以用于过滤检索。
例如:
只在 source = "rag_intro.md" 的文档里检索
只在 user_id = 1001 的用户文档里检索
只在 chapter = "文本分块" 的章节里检索
4.4 一个典型 RAG Schema
| 字段名 | 类型 | 作用 |
|---|---|---|
| id | INT64 | 主键 |
| chunk_text | VARCHAR | 原始文本 chunk |
| embedding | FLOAT_VECTOR | 向量字段 |
| source | VARCHAR | 来源文件 |
| page | INT64 | 页码 |
| chapter | VARCHAR | 所属章节 |
| chunk_index | INT64 | chunk 顺序 |
一句话总结:
Milvus Schema 就是向量数据库里的表结构,它规定每条向量数据应该长什么样。
5. 向量索引和 MySQL B+Tree 索引的本质区别
5.1 MySQL B+Tree 索引解决什么问题?
MySQL B+Tree 适合有序字段。
例如:
SELECT * FROM user WHERE id = 1001;
或者:
SELECT * FROM user WHERE age BETWEEN 18 AND 30;
这些字段天然有大小关系:
1 < 2 < 3 < 4
所以可以建树、排序、范围查找。
5.2 向量索引解决什么问题?
向量可能是这样的:
[0.12, -0.35, 0.88, 0.04, ...]
它有几百维甚至上千维。
你很难说:
向量 A > 向量 B
这个比较本身没有意义。
向量数据库关心的是:
向量 A 和向量 B 距离近不近?
方向像不像?
语义是否相似?
所以向量索引的目标不是“排序查找”,而是:
最近邻搜索 Nearest Neighbor Search
5.3 最近邻搜索是什么?
为了方便理解,把向量简化成二维坐标。
文档 A:RAG 是检索增强生成 -> [1.0, 1.1]
文档 B:向量数据库用于相似度搜索 -> [1.2, 1.0]
文档 C:Redis 是内存数据库 -> [8.0, 8.2]
文档 D:MySQL 使用 B+Tree 索引 -> [7.5, 7.8]
用户问:
向量数据库是做什么的?
问题向量可能是:
query -> [1.1, 1.0]
它离 A、B 比较近,离 C、D 比较远。
向量索引就是为了快速找到这些最近的点。
6. FLAT:全量扫描索引
6.1 FLAT 的存储结构
FLAT 基本没有复杂索引结构。
它就是把所有原始向量顺序存起来。
类似 Java:
List<float[]> vectors;
示意:
vector_id = 1 -> [0.12, -0.35, 0.88, ...]
vector_id = 2 -> [0.21, -0.11, 0.76, ...]
vector_id = 3 -> [0.89, 0.43, -0.22, ...]
6.2 FLAT 的查询方式
查询时:
query_vector 和 vector 1 比较
query_vector 和 vector 2 比较
query_vector 和 vector 3 比较
...
query_vector 和所有向量都比较
然后排序,取 topK。
6.3 为什么 FLAT 准确率最高?
因为它没有跳过任何数据。
如果有 100 万条向量,它会真的和 100 万条都算相似度。
所以它一定能找到真正的 topK。
这就像 MySQL 的全表扫描:
所有行都看一遍,所以不会漏。
6.4 为什么 FLAT 慢?
因为每次查询都要扫描所有向量。
假设:
100 万条向量
每条 768 维
一次查询大概要做:
1000000 × 768 次数值计算
数据越大,越慢。
6.5 FLAT 的优缺点来源
存储结构:
原始向量数组
搜索方式:
全量扫描
所以:
优点
缺点
一句话:
FLAT = 向量数据库里的全表扫描。
7. IVF_FLAT:聚类中心 + 倒排桶
IVF_FLAT 是向量数据库中非常经典的索引。
可以理解成:
先对向量空间分桶,查询时先找桶,再在桶里精确扫描。
7.1 IVF_FLAT 的内部存储结构
IVF_FLAT 主要包含两部分:
示意:
centroid 1 -> [v1, v2, v3]
centroid 2 -> [v4, v5]
centroid 3 -> [v6, v7, v8]
每个 centroid 代表向量空间中的一个区域。
每个 centroid 下面挂着一批属于这个区域的向量。
7.2 为什么叫倒排?
传统搜索引擎里的倒排索引是:
term -> doc list
例如:
Java -> doc1, doc2
Redis -> doc1, doc3
MySQL -> doc2
它不是从文档找词,而是从词找文档。
IVF 里面没有“词”,所以它用“聚类中心 / 向量桶”代替词。
IVF 的倒排结构是:
centroid / cluster -> vector list
也就是:
cluster_1 -> vector 1, vector 2, vector 3
cluster_2 -> vector 4, vector 5
所以 IVF 的“倒排”体现在:
不是 vector_id -> vector
而是 cluster_id -> vector_id list
一句话:
传统倒排索引:词 -> 文档列表
IVF 倒排索引:聚类中心 -> 向量列表
7.3 向量桶是如何自动形成的?
IVF 里的向量桶不是人工指定的,不是你手动告诉它:
这个桶放 RAG
那个桶放 Redis
另一个桶放 Java
它是通过聚类算法自动形成的。
最常见的方法是:
K-Means 聚类
你设置:
nlist = 1000
就表示:
把向量空间自动分成 1000 个桶,训练出 1000 个聚类中心。
7.4 K-Means 如何训练聚类中心?
假设有 9 个二维向量:
v1:RAG 定义 -> [1.0, 1.1]
v2:RAG 工作流程 -> [1.2, 1.0]
v3:向量数据库 -> [1.3, 1.2]
v4:Redis 缓存 -> [5.0, 5.2]
v5:MySQL 索引 -> [5.2, 5.1]
v6:数据库事务 -> [5.1, 4.9]
v7:Spring Boot Controller -> [9.0, 1.1]
v8:Java 线程池 -> [9.1, 1.0]
v9:JVM 内存模型 -> [8.9, 1.2]
这些向量大概自然分成三团:
RAG / AI 一团
数据库一团
Java 后端一团
如果 nlist=3,K-Means 会训练出 3 个中心。
第一步:随机初始化 3 个中心
center A
center B
center C
第二步:每个向量找最近的中心
v1 距离 center A 最近 -> 分到 A 桶
v2 距离 center A 最近 -> 分到 A 桶
v4 距离 center B 最近 -> 分到 B 桶
v7 距离 center C 最近 -> 分到 C 桶
第三步:更新中心
每个中心移动到桶内向量的平均位置。
例如 A 桶:
v1 = [1.0, 1.1]
v2 = [1.2, 1.0]
v3 = [1.3, 1.2]
新的 center A:
center A = 平均(v1, v2, v3)
= [1.17, 1.10]
第四步:重复分配和更新
分配向量 -> 更新中心 -> 再分配 -> 再更新
直到中心基本稳定。
最终得到:
centroid 1 -> v1, v2, v3
centroid 2 -> v4, v5, v6
centroid 3 -> v7, v8, v9
这就是 IVF 的向量桶。
7.5 IVF_FLAT 的查询过程
用户问:
RAG 的工作流程是什么?
流程:
如果:
nprobe = 1
表示只搜最近的 1 个桶。
如果:
nprobe = 5
表示搜最近的 5 个桶。
7.6 nlist 和 nprobe
nlist
表示建索引时分多少个桶。
nlist 越大:
桶越多,每个桶越小,桶内扫描更快。
但相关向量可能被分散,需要更大的 nprobe。
nprobe
表示查询时搜多少个桶。
nprobe 越大:
召回率越高,但查询越慢。
nprobe 越小:
查询越快,但可能漏结果。
7.7 IVF_FLAT 为什么快?
因为它不全量扫描。
假设有 100 万条向量,分成 1000 个桶。
每个桶平均 1000 条。
如果查询时只搜 5 个桶:
5 × 1000 = 5000 条
原本 FLAT 要比较:
100 万条
现在只比较:
5000 条
所以快很多。
7.8 IVF_FLAT 为什么可能不准?
因为它只搜部分桶。
如果真正最相似的向量在第 6 个桶,但你只搜前 5 个桶,那么它就被漏掉了。
所以 IVF_FLAT 是近似检索。
核心取舍是:
搜的桶越少:越快,但越容易漏
搜的桶越多:越准,但越慢
7.9 IVF_FLAT 为什么需要训练?
因为它需要先从已有向量中学习出聚类中心。
这里的训练不是训练大模型,也不是训练 embedding 模型。
它训练的是:
IVF 索引自己的聚类中心。
也就是:
如何把当前这批向量空间切成 nlist 个区域。
7.10 IVF_FLAT 的优缺点来源
存储结构:
聚类中心 + 倒排列表 + 桶内原始向量
搜索方式:
先找最近桶,再在桶内 FLAT 扫描
优点
缺点
一句话:
IVF_FLAT = 先按向量空间分桶,再只在相关桶里做精确扫描。
8. IVF_SQ8:IVF + 向量压缩
IVF_SQ8 可以理解成:
IVF_FLAT + 向量压缩
其中:
SQ8 = Scalar Quantization 8-bit
8.1 IVF_SQ8 的存储结构
IVF_FLAT:
centroid -> 原始 float32 向量列表
IVF_SQ8:
centroid -> 压缩后的 8-bit 向量列表
示意:
centroid 1 -> [压缩后的 v1, 压缩后的 v2, 压缩后的 v3]
centroid 2 -> [压缩后的 v4, 压缩后的 v5]
8.2 为什么 IVF_SQ8 更省内存?
假设一个向量是 768 维。
如果每一维是 float32:
float32 = 4 字节
768 × 4 = 3072 字节 ≈ 3KB
如果压缩成 8-bit:
每维约 1 字节
向量部分理论上可以接近节省 4 倍空间。
8.3 为什么 IVF_SQ8 精度会下降?
因为压缩会带来量化误差。
例如原始值:
0.123456
压缩后可能只能表示成近似值:
0.12
这种误差会影响相似度计算,导致排序可能发生变化。
8.4 IVF_SQ8 需要训练什么?
它不仅需要训练 IVF 的聚类中心,还需要训练量化参数。
所以:
IVF_FLAT:训练聚类中心
IVF_SQ8:训练聚类中心 + 量化参数
8.5 IVF_SQ8 的优缺点来源
存储结构:
IVF 分桶 + 桶内压缩向量
搜索方式:
先找桶,再用压缩向量做近似距离计算
优点
缺点
一句话:
IVF_SQ8 = 先分桶,再压缩桶里的向量,用空间换一点精度。
9. HNSW:多层近邻图索引
HNSW 是非常常用的向量索引。
全称是:
Hierarchical Navigable Small World
不用死记英文,重点理解:
HNSW 会把向量组织成一张多层近邻图,查询时像导航一样快速跳到目标附近。
9.1 HNSW 的图结构是什么样?
HNSW 把每个向量当成一个节点。
每个节点会连接若干个相似节点。
类似 Java 对象:
class Node {
long id;
float[] vector;
List<Long> neighbors;
}
单层图可以理解成:
A(RAG定义) —— B(RAG流程) —— C(向量数据库)
|
D(Redis缓存) —— E(MySQL索引)
|
F(SpringBoot)
边表示:
两个向量比较接近。
9.2 HNSW 为什么是多层图?
HNSW 不是只有一层图,而是多层图。
可以想象成地图:
高速公路层:节点少,跳得远
主干道层:节点多一些
城市小路层:节点最多,找得细
示意:
Level 2: A -------- H
\ /
\ /
Level 1: A --- C --- F --- H
| | | |
Level 0: A - B - C - D - E - F - G - H
Level 0
最底层,包含所有向量点。
Level 1 / Level 2
越高层节点越少,用于快速跳转。
9.3 HNSW 查询过程
用户问:
向量数据库如何做相似度检索?
查询过程:
它不是全量扫描,而是沿着图中“越来越相似”的方向走。
9.4 HNSW 的重要参数
M
每个节点最多连接多少个邻居。
M 越大:
图更密,召回更高,但内存更大,构建更慢。
efConstruction
构建索引时搜索候选邻居的范围。
efConstruction 越大:
建图更认真,图质量更好,但构建更慢。
efSearch
查询时维护多少候选节点。
efSearch 越大:
搜得更广,召回更高,但查询延迟更高。
9.5 HNSW 为什么快?
因为它通过图导航快速接近目标区域。
它不需要和所有向量比较。
多层结构让它可以:
先在高层快速跳到大概区域
再在底层精细搜索
9.6 HNSW 为什么召回高?
因为它不像 IVF 那样先强行分桶。
IVF 如果桶选错,可能直接漏掉。
HNSW 是沿着近邻图不断探索,只要图质量好、efSearch 足够大,就能获得较高召回。
9.7 HNSW 为什么吃内存?
因为它除了存原始向量,还要存图的边。
每个节点都要存:
自己的向量
Level 0 的邻居列表
Level 1 的邻居列表
Level 2 的邻居列表
...
如果有很多向量,每个向量还连几十条边,这些边会占用大量内存。
9.8 HNSW 需要训练吗?
HNSW 不需要训练聚类中心。
它需要的是:
构建近邻图。
它不是先学习 centroid,而是在插入向量时,找到近邻并连边。
所以:
是否需要训练:不需要
是否需要构建:需要建图
9.9 HNSW 的优缺点来源
存储结构:
原始向量 + 多层近邻图 + 每个节点的邻居列表
搜索方式:
从高层入口开始,沿图贪心搜索,逐层下降到底层
优点
缺点
一句话:
HNSW = 把向量建成多层导航图,用内存和构建成本换低延迟和高召回。
10. DiskANN:面向超大规模的 SSD 图索引
DiskANN 可以理解成:
为超大规模向量数据设计的 SSD 友好型图索引。
10.1 为什么需要 DiskANN?
HNSW 很快,但它很吃内存。
如果有:
1 亿条向量
每条 768 维
仅原始向量就可能占用几百 GB,更不要说图边。
这时全部放内存成本很高,甚至放不下。
DiskANN 的思路是:
大量向量和邻接信息放 SSD
内存里只保留必要的导航结构和缓存
10.2 DiskANN 的存储结构
简化理解:
内存:
1. 少量入口点
2. 压缩后的导航信息
3. 热点缓存
SSD:
1. 原始或压缩向量
2. 图的邻接列表
3. 大部分索引数据
10.3 DiskANN 的查询方式
查询时:
它不会从 SSD 全量扫描,而是只读取搜索路径上需要的节点。
10.4 DiskANN 的优缺点来源
存储结构:
内存导航结构 + SSD 上的大规模向量和图邻接信息
搜索方式:
内存定位方向,SSD 读取相关节点,图搜索扩展
优点
缺点
一句话:
DiskANN = 为超大规模向量设计的 SSD 友好型图索引。
11. 哪些索引需要训练,哪些不需要
这里要区分两个概念:
训练索引:从向量数据中学习出参数,比如聚类中心、量化码本。
构建索引:把向量组织成某种结构,比如建图、分桶、保存数组。
不是所有索引都需要训练,但几乎所有索引都需要构建。
11.1 不需要训练的索引
FLAT
不需要训练
基本只是保存原始向量
查询时全量扫描
HNSW
不需要训练聚类中心
但需要构建多层近邻图
DiskANN
通常不叫训练
不需要训练聚类中心
但需要构建 SSD 友好的图索引
11.2 需要训练的索引
IVF_FLAT
需要训练:
K-Means 聚类中心
IVF_SQ8
需要训练:
聚类中心 + 量化参数
IVF_PQ
需要训练:
聚类中心 + PQ 码本
11.3 总结表
| 索引类型 | 是否需要训练 | 训练什么 | 是否需要构建索引 |
|---|---|---|---|
| FLAT | 不需要 | 无 | 基本只是保存向量 |
| HNSW | 不需要 | 无 | 需要建多层近邻图 |
| DiskANN | 通常不叫训练 | 无聚类中心 | 需要构建 SSD 图索引 |
| IVF_FLAT | 需要 | K-Means 聚类中心 | 需要分桶 |
| IVF_SQ8 | 需要 | 聚类中心 + 量化参数 | 需要分桶和压缩 |
| IVF_PQ | 需要 | 聚类中心 + PQ 码本 | 需要分桶和编码 |
判断标准:
凡是需要先学习“空间划分规则”或“压缩规则”的索引,通常都需要训练。
凡是直接全扫或直接建图的索引,通常不叫训练。
12. IVF_FLAT 训练耗时与影响因素
12.1 IVF_FLAT 训练耗时来自哪里?
IVF_FLAT 的训练耗时本质上来自:
K-Means 聚类
也就是:
从一批文档向量中训练出 nlist 个聚类中心。
12.2 影响耗时的因素
主要包括:
可以粗略理解为:
训练成本 ≈ 训练样本数 × 向量维度 × nlist × 迭代次数
12.3 经验耗时范围
| 数据规模 | 维度 | nlist | 大概耗时 |
|---|---|---|---|
| 几千 ~ 几万条 | 512 / 768 | 64 ~ 256 | 秒级 |
| 10 万 ~ 100 万条 | 512 / 768 | 256 ~ 4096 | 几十秒到几分钟 |
| 100 万 ~ 1000 万条 | 768 / 1024 | 4096 ~ 16384 | 几分钟到几十分钟 |
| 亿级向量 | 768+ | 16384+ | 可能几十分钟到数小时 |
这只是经验范围,实际耗时取决于机器性能和数据库实现。
12.4 训练时间和建索引时间的区别
IVF_FLAT 完整建索引通常包含:
阶段 1:训练聚类中心
阶段 2:把所有向量分配到最近的桶
阶段 3:写入索引结构
所以你看到的“建索引耗时”不一定全是训练耗时。
13. 新增数据后是否需要重建索引
13.1 文档库不变时
如果:
文档库不变
embedding 模型不变
nlist 等索引参数不变
那么:
聚类中心不需要重新训练。
13.2 少量新增数据
如果只是少量新增,并且新增内容和原数据分布类似,一般不需要重建。
例如:
原来是 Java 后端知识库
新增的还是 Java / Spring / Redis 文档
新向量会被分配到最近的已有 centroid 桶里。
13.3 大量新增且分布变化大
如果新增数据很多,并且分布明显变化,就建议重建。
例如:
原来主要是 RAG / LLM / 向量数据库
后来大量新增 Java / Spring / Redis / MySQL / Kafka
旧 centroid 可能不再代表整体数据分布。
可能出现:
这时需要重新训练聚类中心并重建索引。
13.4 必须重建的情况
1. 更换 embedding 模型
例如:
原来用 bge-small-zh-v1.5,512 维
后来换成 bge-base-zh-v1.5,768 维
旧向量和新向量不在同一个向量空间里,必须:
重新 embedding 所有文档
重新训练索引
重新构建向量库
2. 修改 nlist 等索引参数
如果:
nlist = 1024
改成:
nlist = 4096
桶数量变了,聚类中心也要重新训练。
3. 新增数据远大于原数据
例如原来 10 万条,后来变成 200 万条,旧 centroid 可能不再适合。
13.5 最终理解
可以这样记:
IVF_FLAT 的聚类中心是对文档向量空间的一次提前分区。
如果数据分布不变,分区不用重建。
如果数据分布大变,就应该重新分区。
14. 各类向量索引优缺点总表
| 索引 | 存储结构 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|---|
| FLAT | 原始向量数组 | 最准确,不需要训练 | 慢,数据大时不适合在线 | 小数据、评估基准 |
| IVF_FLAT | 聚类中心 + 倒排桶 + 原始向量 | 比 FLAT 快,可调 nprobe | 需要训练,可能漏召回 | 中大规模数据 |
| IVF_SQ8 | IVF + 压缩向量 | 更省内存 | 有量化误差,精度略降 | 内存紧张场景 |
| HNSW | 原始向量 + 多层近邻图 | 低延迟,高召回,不需要训练聚类中心 | 内存大,构建慢 | 在线 RAG、低延迟检索 |
| DiskANN | SSD 图索引 + 内存导航 | 支持超大规模,内存成本低 | 依赖 SSD,复杂,延迟高于内存索引 | 亿级向量、超大规模检索 |
15. 实战选型建议
15.1 小数据量 / Demo
推荐:
FLAT
原因:
简单、准确、不需要训练。
15.2 中小型在线 RAG
推荐:
HNSW
原因:
查询快,召回高,适合在线问答。
前提:
数据能放进内存。
15.3 中大规模数据,追求速度和资源平衡
推荐:
IVF_FLAT
原因:
通过分桶减少扫描量,速度和召回可通过 nprobe 调节。
15.4 内存紧张,可以接受轻微精度损失
推荐:
IVF_SQ8
原因:
向量压缩后更省内存。
15.5 超大规模,内存放不下
推荐:
DiskANN
原因:
大量数据放 SSD,内存只保留导航结构。
15.6 Java 程序员类比记忆
| 向量索引 | 类比 |
|---|---|
| FLAT | MySQL 全表扫描 |
| IVF_FLAT | 自动分区后查分区 |
| IVF_SQ8 | 分区 + 压缩 |
| HNSW | 内存多层导航图 |
| DiskANN | SSD 上的大规模图索引 |
16. 问题追踪:你的追问路径与对应答案
这一节按照你的提问顺序整理,方便复盘自己的理解路径。
Q1:FAISS 到底是不是数据库?
答案:
FAISS 不是完整数据库,而是向量相似度检索库 / 向量索引库。
它擅长:
给定 query 向量,快速找最相似的向量。
它不提供传统数据库的 SQL、事务、权限、网络服务等能力。
Q2:FAISS 生成的 index.faiss 和 index.pkl 是什么?
答案:
index.faiss:
保存真正的向量索引,用来找相似 vector_id。
index.pkl:
保存 LangChain 的文档内容、metadata、vector_id 到 Document 的映射。
一句话:
index.faiss 负责找向量,index.pkl 负责找回原文。
Q3:Milvus Schema 是什么?
答案:
Schema 是 Milvus Collection 的表结构设计图。
它定义:
有哪些字段
字段类型是什么
哪个字段是主键
哪个字段是向量字段
哪些字段是 metadata
Q4:向量索引和 MySQL 索引有什么不同?
答案:
MySQL B+Tree 主要解决精确匹配和范围查询。
向量索引解决的是:
在高维向量空间中找到和 query 向量最相似的 topK。
MySQL 查的是:
等于谁 / 大于谁
向量数据库查的是:
最像谁
Q5:IVF_FLAT 为什么叫倒排?
答案:
传统倒排索引是:
term -> doc list
IVF 的倒排结构是:
centroid / cluster -> vector list
它不是从 vector 找 cluster,而是从 cluster 找这个桶里的 vector 列表。
所以也叫倒排。
Q6:IVF 的向量桶是如何自动形成的?
答案:
通过 K-Means 聚类。
流程:
Q7:聚类中心是如何训练的?
答案:
通过 K-Means:
这里训练的是 IVF 索引的空间划分规则,不是训练 embedding 模型。
Q8:HNSW 的图结构是什么样?
答案:
HNSW 是多层近邻图。
高层:节点少,跳得远,负责快速定位
底层:节点多,包含所有向量,负责精细搜索
查询时像导航:
高速路 -> 主干道 -> 小路
Q9:为什么 HNSW 快但吃内存?
答案:
快是因为它沿近邻图导航,不全量扫描。
吃内存是因为每个节点除了存向量,还要存邻居列表和多层图边。
Q10:哪些索引需要训练,哪些不需要?
答案:
不需要训练:
FLAT
HNSW
DiskANN 通常不叫训练聚类中心
需要训练:
IVF_FLAT:训练聚类中心
IVF_SQ8:训练聚类中心 + 量化参数
IVF_PQ:训练聚类中心 + PQ 码本
Q11:IVF_FLAT 训练通常需要多久?
答案:
取决于:
向量数量
维度
nlist
迭代次数
硬件
是否采样训练
经验上:
小数据:秒级
中等数据:几十秒到几分钟
百万到千万级:几分钟到几十分钟
亿级:可能更久,需要 GPU / 分布式 / 抽样
Q12:如果文档库不变,聚类中心需要重建吗?
答案:
不需要。
如果:
文档库不变
embedding 模型不变
nlist 不变
聚类中心可以一直复用。
Q13:如果文档库新增很多内容,需要重建吗?
答案:
看情况。
如果只是少量新增,且数据分布类似,通常不需要。
如果新增很多,而且数据分布变化明显,就建议重新训练 / 重建索引。
例如:
原来全是 RAG / LLM 文档
后来大量加入 Java / Redis / MySQL 文档
旧聚类中心可能不适合新数据分布。
17. 最终记忆口诀
可以用下面几句话记住:
FLAT:
全量扫描,最准但慢,不需要训练。
IVF_FLAT:
先训练聚类中心,把向量分桶;查询时先找桶,再桶内精确扫描。
IVF_SQ8:
IVF_FLAT 加压缩,更省内存,但有量化误差。
HNSW:
多层近邻图,像地图导航,查询快、召回高,但吃内存。
DiskANN:
把大规模图索引放到 SSD,适合超大数据,但比纯内存慢。
需要训练的索引:
凡是要学习“分桶规则”或“压缩规则”的,一般都要训练。
不需要训练的索引:
直接全扫或直接建图的,一般不叫训练。
最终一句话:
向量索引的所有优缺点,本质上都来自它的底层存储结构:是全量数组、分桶倒排、压缩倒排、多层图,还是 SSD 图索引。理解了结构,就能理解为什么它快、为什么它省内存、为什么它可能漏召回,以及为什么有的需要训练、有的不需要训练。

浙公网安备 33010602011771号