RAG 系列 4:向量数据库基础与 FAISS、Chroma、Milvus 选型


上一篇使用 CSV 保存了 100 条评论及其 Embedding,然后在查询时逐行读取向量、计算余弦相似度并排序。这个方法容易理解,也能帮助我们验证语义搜索的完整过程,但数据量继续增加后会遇到几个问题:

  1. 每次查询都要扫描全部向量。
  2. 向量、原始文本和元数据缺少统一管理。
  3. 新增、更新和删除数据比较麻烦。
  4. 进程重启后,需要自己处理索引与数据的恢复。
  5. 很难同时完成向量检索与分类、时间等标量条件过滤。

专门的向量检索系统就是为这些问题准备的,但不同产品的能力边界并不相同。FAISS 主要解决向量索引和检索,Chroma 和 Milvus 还会管理文本、元数据、持久化和增删改查。

这一篇先把基本概念讲清楚,再比较 FAISS、Chroma 和 Milvus。后面三篇分别使用它们的原生 API 完成真实检索,暂时不与 LangChain、Retriever 或 RAG 结合。

1. 向量数据库保存的是什么

Embedding 模型负责把文本转换成向量,向量数据库负责保存和检索这些向量。二者不是同一种工具。

一条完整的向量记录通常包含下面几部分:

内容 示例 作用
ID doc-1 唯一标识一条记录
Vector 1024 个浮点数 用于计算距离或相似度
原始文本 西湖边有步道和树荫 检索后返回给业务程序
Metadata category=travel 用于标量过滤和业务管理

向量数据库存储与检索流程

图中的写入路径和查询路径是两个不同阶段。写入时会把文档向量、ID、原文和元数据作为记录保存;查询时只生成查询向量,用它搜索索引,不会把查询自动写成新记录。

可以把写入过程理解为:

原始文本
-> Embedding 模型
-> 向量
-> 向量数据库

查询过程则是:

用户问题
-> 同一个 Embedding 模型
-> 查询向量
-> 向量索引检索
-> 返回最相近的 Top-K 记录

查询向量与文档向量必须来自同一个模型,并使用兼容的编码方式。不同模型产生的向量空间不同,即使维度相同,也不能直接比较。

2. Collection、记录和索引

关系型数据库通常使用 Database、Table 和 Row 组织数据。向量数据库经常使用 Collection 和 Entity,也有产品继续使用 Table、Point 或 Record 等名称。

它们的名字不同,但可以先按下面的方式理解:

Database
└── Collection
    ├── 记录 1:ID + Vector + Text + Metadata
    ├── 记录 2:ID + Vector + Text + Metadata
    └── 向量索引

Collection 不只是一个向量列表。它通常还规定:

  1. 向量维度,例如 Qwen3-Embedding-0.6B 默认输出 1024 维。
  2. 距离度量,例如 COSINE、L2 或 IP。
  3. 索引类型和索引参数。
  4. 主键和其他标量字段。

向量维度一旦确定,写入的每条向量都必须满足这个维度。把 1024 维向量写入 768 维 Collection 会直接失败。

这是向量数据库的通用理解,不代表三个产品都使用相同的对象名称。例如 FAISS 的核心对象是 Index,它没有 Database 和 Collection 这层数据库结构,文本和元数据也需要由应用自己保存。

3. 精确检索和近似最近邻

在 RAG 系列 3 中,我们把查询向量和每条评论向量都计算一次相似度。这属于全量扫描,也可以看成精确检索。

精确检索会检查所有候选向量,结果准确,但计算量会随着数据量线性增长。当向量数量很大时,向量数据库通常会使用近似最近邻检索,也就是 ANN(Approximate Nearest Neighbor)。

ANN 不保证每次都找到理论上的绝对最优结果,而是通过索引减少需要比较的向量数量,以换取更低的查询延迟。

精确检索与近似最近邻检索对比

“精确”只表示在已选定的向量、距离度量和候选集中,系统会找到真正的 Top-K。它不能保证 Embedding 模型对语义的表达一定正确,也不能代替业务规则。

选择索引时通常需要平衡下面几个指标:

指标 含义
召回率 理论最相关结果中,有多少被实际找回
查询延迟 一次检索需要多长时间
内存占用 索引和向量在内存中占多少空间
建索引成本 创建或更新索引需要多少时间和资源

本系列的数据只有四条,因此 FAISS 使用精确索引 IndexFlatIP。Chroma 使用默认的 HNSW 索引,Milvus 的快速创建接口也会为 Collection 建立索引。小数据示例的重点是理解 API 和数据结构,不是比较性能。

4. 三种常见距离度量

4.1 余弦相似度

余弦相似度比较两个向量的方向:

$$
\cos(\theta)=\frac{A \cdot B}{|A||B|}
$$

分数越大,方向越接近。文本语义检索经常使用这种度量。

如果向量已经归一化为单位向量,那么:

$$
|A|=|B|=1
$$

此时内积就等于余弦相似度:

$$
A \cdot B=\cos(\theta)
$$

4.2 内积

内积通常写作 IP(Inner Product),分数越大越接近。它会同时受到方向和向量长度影响。

本系列先对 Qwen3 向量执行归一化,再使用 FAISS 的 IndexFlatIP。因此返回值可以直接看成余弦相似度。

4.3 L2 距离

L2 距离就是欧氏距离。FAISS 的 IndexFlatL2 返回平方 L2 距离,数值越小越接近。

这里要特别注意:

IndexFlatL2 -> L2 距离,越小越接近
IndexFlatIP -> 内积,越大越接近
归一化向量 + IndexFlatIP -> 余弦相似度,越大越接近

不能把 IndexFlatL2 的返回值直接称为余弦相似度,也不能用相同的分数方向解释所有数据库。

还要区分“度量的数学定义”和“API 返回的字段语义”。本系列后面使用的三个接口并不统一:

接口 实际含义 更接近的方向
FAISS IndexFlatL2 平方 L2 距离 越小越接近
FAISS IndexFlatIP 内积;单位向量时等于余弦相似度 越大越接近
Chroma space=”cosine” 余弦距离 1 - cosine_similarity 越小越接近
Milvus metric_type=”COSINE” 余弦相似度,虽然 PyMilvus 命中字段名为 distance 越大越接近

因此,不要只看返回字段叫 score 还是 distance,而要查看当前产品、索引和度量的定义。

5. FAISS 是什么

FAISS 是面向稠密向量相似度搜索和聚类的库。它提供多种索引,可以在 Python 或 C++ 程序中直接使用。

它的特点是:

  1. 不需要启动独立数据库服务。
  2. 向量索引能力强,适合本地实验和嵌入应用。
  3. 可以把索引写入磁盘再重新加载。
  4. 原生索引主要管理向量和位置编号,文本、元数据、权限和事务需要应用自己处理。

因此,FAISS 经常被称为向量数据库,但从产品边界看,它更准确的定位是向量检索库。它不像 MySQL 或 Milvus 那样提供完整的数据库服务。

6. Chroma 是什么

Chroma 是面向 AI 应用的开源向量数据库。它可以通过 PersistentClient 直接在本地目录中运行,也支持客户端连接服务端。

与原生 FAISS 相比,Chroma 可以直接管理:

  1. ID。
  2. 向量。
  3. Document 原文。
  4. Metadata 元数据。
  5. Collection。
  6. 元数据过滤和增删改查。

本地持久化模式会生成 chroma.sqlite3 和 HNSW 索引文件。程序重新创建 PersistentClient 后,可以继续读取同一目录中的数据。

7. Milvus 是什么

Milvus 是独立运行的向量数据库,提供数据库、Collection、索引、标量过滤和服务化接口。

Milvus 有三种常见部署形态:

形态 说明
Milvus Lite 嵌入 Python 进程,适合快速试验和小规模本地使用
Standalone 单机服务,组件较完整,适合本地开发和单机部署
Distributed 分布式集群,面向更大的数据规模和生产负载

本系列使用 Standalone。它通过 Docker Compose 启动 Milvus、etcd 和 MinIO 三个容器,Python 使用 MilvusClient 通过 19530 端口连接。

8. FAISS、Chroma 和 Milvus 怎么选

不要只根据“能存多少条向量”选择产品,还要考虑部署、元数据、运维和扩展方式。

FAISS、Chroma 和 Milvus 的选择路径

这张图按本系列实际采用的形态做选择:FAISS 是进程内检索库,Chroma 使用 PersistentClient,Milvus 使用 Standalone 服务。它不是性能排名;Chroma 也可以启动服务端,Milvus 也有嵌入 Python 进程的 Lite 形态。

对比项 FAISS Chroma Milvus Standalone
产品定位 向量检索库 轻量向量数据库 独立向量数据库服务
是否需要服务进程 不需要 本地模式不需要 需要
本地持久化 支持索引文件;文本与元数据另存 支持 支持
原始文本与元数据 应用自行保存 原生支持 原生字段或动态字段
元数据过滤 应用自行实现 支持 支持
增删改查 依赖索引类型和业务代码 API 完整 API 完整
分布式扩展 不提供数据库集群 不作为本文重点 支持分布式形态
适合场景 本地算法验证、嵌入式检索 小型应用、原型、轻量持久化 服务化、较大规模、独立运维

可以先按下面的思路选择:

  1. 只需要本地快速检索,愿意自己管理文本和元数据,使用 FAISS。
  2. 希望本地直接保存文本、向量和元数据,使用 Chroma。
  3. 希望通过独立服务统一管理向量数据,并考虑后续扩展,使用 Milvus。

后面三篇会使用同一组生活化文本、相同的 Qwen3 Embedding 模型和相同的查询来比较 API 与数据结构。数据只有四条,因此这不是三个产品的性能基准测试。

9. 这一阶段不做什么

向量数据库只是负责保存和检索数据。它不会自动完成下面这些工作:

  1. 不会替代 Embedding 模型。
  2. 不会自动理解应该如何切分长文档。
  3. 不会自动把检索结果交给聊天模型。
  4. 不等于 RAG,也不等于知识库应用的全部功能。

本文只使用三种数据库的原生 API。等数据库本身的写入、检索、过滤和持久化都理解清楚后,再学习 LangChain 的 VectorStore、Retriever 和记忆功能会更容易。

10. 小结

从 CSV 全量扫描走向向量数据库,核心变化不是“换了一个保存文件”,而是把向量存储、索引、Top-K 检索、元数据过滤和持久化交给专门的组件。

下一篇先从最轻量的 FAISS 开始,使用本地 Qwen3-Embedding-0.6B 生成真实向量,再通过 IndexFlatIP 完成检索和索引持久化。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录