上一篇使用 CSV 保存了 100 条评论及其 Embedding,然后在查询时逐行读取向量、计算余弦相似度并排序。这个方法容易理解,也能帮助我们验证语义搜索的完整过程,但数据量继续增加后会遇到几个问题:
- 每次查询都要扫描全部向量。
- 向量、原始文本和元数据缺少统一管理。
- 新增、更新和删除数据比较麻烦。
- 进程重启后,需要自己处理索引与数据的恢复。
- 很难同时完成向量检索与分类、时间等标量条件过滤。
专门的向量检索系统就是为这些问题准备的,但不同产品的能力边界并不相同。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 不只是一个向量列表。它通常还规定:
- 向量维度,例如 Qwen3-Embedding-0.6B 默认输出 1024 维。
- 距离度量,例如 COSINE、L2 或 IP。
- 索引类型和索引参数。
- 主键和其他标量字段。
向量维度一旦确定,写入的每条向量都必须满足这个维度。把 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++ 程序中直接使用。
它的特点是:
- 不需要启动独立数据库服务。
- 向量索引能力强,适合本地实验和嵌入应用。
- 可以把索引写入磁盘再重新加载。
- 原生索引主要管理向量和位置编号,文本、元数据、权限和事务需要应用自己处理。
因此,FAISS 经常被称为向量数据库,但从产品边界看,它更准确的定位是向量检索库。它不像 MySQL 或 Milvus 那样提供完整的数据库服务。
6. Chroma 是什么
Chroma 是面向 AI 应用的开源向量数据库。它可以通过 PersistentClient 直接在本地目录中运行,也支持客户端连接服务端。
与原生 FAISS 相比,Chroma 可以直接管理:
- ID。
- 向量。
- Document 原文。
- Metadata 元数据。
- Collection。
- 元数据过滤和增删改查。
本地持久化模式会生成 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 使用 PersistentClient,Milvus 使用 Standalone 服务。它不是性能排名;Chroma 也可以启动服务端,Milvus 也有嵌入 Python 进程的 Lite 形态。
| 对比项 | FAISS | Chroma | Milvus Standalone |
|---|---|---|---|
| 产品定位 | 向量检索库 | 轻量向量数据库 | 独立向量数据库服务 |
| 是否需要服务进程 | 不需要 | 本地模式不需要 | 需要 |
| 本地持久化 | 支持索引文件;文本与元数据另存 | 支持 | 支持 |
| 原始文本与元数据 | 应用自行保存 | 原生支持 | 原生字段或动态字段 |
| 元数据过滤 | 应用自行实现 | 支持 | 支持 |
| 增删改查 | 依赖索引类型和业务代码 | API 完整 | API 完整 |
| 分布式扩展 | 不提供数据库集群 | 不作为本文重点 | 支持分布式形态 |
| 适合场景 | 本地算法验证、嵌入式检索 | 小型应用、原型、轻量持久化 | 服务化、较大规模、独立运维 |
可以先按下面的思路选择:
- 只需要本地快速检索,愿意自己管理文本和元数据,使用 FAISS。
- 希望本地直接保存文本、向量和元数据,使用 Chroma。
- 希望通过独立服务统一管理向量数据,并考虑后续扩展,使用 Milvus。
后面三篇会使用同一组生活化文本、相同的 Qwen3 Embedding 模型和相同的查询来比较 API 与数据结构。数据只有四条,因此这不是三个产品的性能基准测试。
9. 这一阶段不做什么
向量数据库只是负责保存和检索数据。它不会自动完成下面这些工作:
- 不会替代 Embedding 模型。
- 不会自动理解应该如何切分长文档。
- 不会自动把检索结果交给聊天模型。
- 不等于 RAG,也不等于知识库应用的全部功能。
本文只使用三种数据库的原生 API。等数据库本身的写入、检索、过滤和持久化都理解清楚后,再学习 LangChain 的 VectorStore、Retriever 和记忆功能会更容易。
10. 小结
从 CSV 全量扫描走向向量数据库,核心变化不是“换了一个保存文件”,而是把向量存储、索引、Top-K 检索、元数据过滤和持久化交给专门的组件。
下一篇先从最轻量的 FAISS 开始,使用本地 Qwen3-Embedding-0.6B 生成真实向量,再通过 IndexFlatIP 完成检索和索引持久化。