向量数据库:原理讲透再做选型
作者:程序员马丁
Ragent AI —— 从 0 到 1 纯手工打造企业级 Agentic RAG,拒绝 Demo 玩具!AI 时代,助你拿个offer。
上一篇我们把文本 chunk 变成了一组浮点数向量,还用 SiliconFlow 的 API 跑通了一个完整的向量化检索 demo。最后留了一个问题:向量生成之后存到哪里?
当时的 demo 里,我们把所有向量放在一个 List<float[]> 里,查询的时候遍历整个列表,逐个算余弦相似度,取最高的几个。demo 跑得很顺畅,因为只有几条数据。
但你想想实际场景——一个电商平台的知识库,商品说明、退货政策、物流规则、促销活动、FAQ……分块之后轻轻松松几十万甚至上百万个 chunk,每个 chunk 对应一个 4096 维的向量。每次用户提问,你都要拿查询向量和这几十万个向量逐一比较?
这显然不现实。
这一篇,我们就来解决这个问题:向量存到哪里,怎么在海量向量中高效检索。
向量存到哪里:为什么普通数据库不够用
1. 最直觉的方案:用 MySQL 存向量
既然向量就是一组浮点数,那最直觉的想法就是——存 MySQL 呗。
方案很简单:在表里加一个 TEXT 或 JSON 字段,把向量序列化成字符串存进去。检索的时候把所有向量读出来,在应用层逐个计算余弦相似度,排序取 Top-K。
CREATE TABLE chunk_vectors (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
chunk_text TEXT NOT NULL,
vector JSON NOT NULL, -- 存储 4096 维浮点数向量
doc_id VARCHAR(64),
category VARCHAR(32),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
能跑通吗?能。demo 阶段完全没问题。
但这个方案有一个致命的问题——检索的时候,你必须把所有向量都读出来,在内存里逐个计算相似度。这 就是所谓的暴力搜索(Brute-Force Search)。
2. 暴力搜索的性能瓶颈
咱们算一笔账。
假设你的知识库有 100 万个 chunk,每个 chunk 的向量是 4096 维(用的 Qwen3-Embedding-8B 模型)。每次用户提问,系统需要:
- 把用户的问题也向量化,得到一个 4096 维的查询向量
- 从数据库里读出 100 万个向量
- 逐个计算查询向量和这 100 万个向量的余弦相似度
- 排序,取相似度最高的 Top-K 个
第 3 步是瓶颈。每次余弦相似度计算需要做 4096 次乘法 + 4096 次加法 + 开方等运算。100 万个向量就是 100 万次这样的计算。
具体要多久?在一台普通服务器上(单核 CPU),100 万个 4096 维向量的暴力搜索大约需要 2~5 秒。听起来好像还行?
但别忘了:
- 这只是单次查询的耗时,如果有 100 个用户同时提问呢?
- 数据量还会增长,500 万、1000 万个 chunk 呢?
- 实际的 RAG 系统对延迟很敏感,用户问一个问题不只是向量检索,还有很多其他步骤,体验很差
- 而且每次查询都要把 100 万个向量从磁盘读到内存,I/O 开销也很大
用一张图来直观感受一下暴力搜索的过程:

所以,暴力搜索在数据量小的时候没问题,但一旦数据量和并发量上去,就完全不可用了。
3. 近似最近邻搜索
既然逐个比较太慢,能不能不比较所有向量,只比较其中一部分,就找到大概率最相似的那几个?
答案是可以。这就是 ANN(Approximate Nearest Neighbor,近似最近邻搜索)的核心思想。
注意这里的关键词是“近似”——ANN 不保证找到的一定是全局最相似的向量,但它能在极短的时间内找到非常接近最优解的结果。
打个比方:暴力搜索就像你要在一个 100 万人的城市里找和你最像的人,挨个去比对。ANN 则是先按区域划分,再按特征缩小范围,最后只在一小撮人里精确比较。你可能会错过某个住在偏远角落的“最佳匹配”,但你找到的人已经足够像了,而且速度快了几百倍。
用数字来感受一下差距:
| 指标 | 暴力搜索 | ANN 检索 |
|---|---|---|
| 100 万向量查询耗时 | 2~5 秒 | 1~10 毫秒 |
| 召回率(Recall) | 100%(精确) | 95%~99%(近似) |
| 是否需要专门索引 | 不需要 | 需要 |
| 适用数据量 | < 10 万 | 百万~亿级 |
110 毫秒 vs 25 秒,速度差了几百到几千倍,而召回率只损失了 1%~5%。在实际的 RAG 场景中,这点精度损失几乎感知不到——你本来就是取 Top-K 个结果丢给大模型做参考,少了一个排名第 47 的 chunk 对最终回答没有影响。
这就是向量数据库存在的核心理由:它不只是存向量,更重要的是提供高效的 ANN 检索能力。普通数据库能存向量,但做不了高效的 ANN 检索。
一句话概括:向量数据库 = 向量存储 + ANN 索引 + 高效检索。它是专门为“在海量向量中快速找到最相似的那几个”这件事而设计的。
向量检索的核心算法:怎么不用逐个比较就能找到最相似的
知道了 ANN 的目标——不逐个比较,快速找到近似最优解——接下来的问题是:具体怎么做到的?
这一节我们讲两种最主流的 ANN 索引算法:IVF 和 HNSW。不涉及数学推导,重点是让你理解它们的核心思想和工程上的取舍。
1. 类比:在一本 10 万页的字典里查一个词
在讲具体算法之前,先想一个生活中的场景。
你手上有一本 10 万页的字典,要查 serendipity 这个词。你会怎么做?
肯定不会从第 1 页翻到第 100000 页。你会:
- 先看目录,确定 S 开头的词在哪个范围
- 翻到大概的位置,再根据前几个字母缩小范围
- 最后在一小段页面里精确查找
这个过程的本质是:通过某种结构(目录、索引),把搜索范围从“全部”缩小到“一小部分”,然后在小范围内精确查找。
向量索引的思路完全一样——只不过字典里的“词”变成了“向量”,字母顺序变成了空间位置。
2. IVF(倒排文件索引):先分区再搜索
IVF 的全称是 Inverted File Index(倒排文件索引)。名字听起来很学术,但思路非常直觉。
2.1 IVF 的工作原理
IVF 的核心思想就一句话:把向量空间划分成若干个区域,查询时只在最可能的几个区域里搜索。
具体怎么做?分两个阶段:
建索引阶段(离线):
- 用聚类算法(通常是 K-Means)把所有向量分成
nlist个簇(cluster) - 每个簇有一个中心点(centroid),代表这个簇里所有向量的"平均位置"
- 每个向量被分配到离它最近的那个簇
检索阶段(在线):
- 拿到查询向量后,先计算它和所有簇中心点的距离
- 找到最近的
nprobe个簇(nprobe 是一个可调参数) - 只在这
nprobe个簇里的向量中做精确搜索
打个比方:你要在一个大型图书馆里找一本关于“Java 并发编程”的书。IVF 的做法是——先看楼层指引,确定计算机类在 3 楼,编程语言在 3 楼 A 区,然后只在 3 楼 A 区的书架上找。你不需要把整个图书馆的书都翻一遍。

假设 nlist = 100(分成 100 个簇),nprobe = 10(查询时搜索 10 个簇),那 么每次查询只需要搜索大约 10% 的向量,速度提升约 10 倍。
2.2 IVF 的优缺点
| 优点 | 缺点 |
|---|---|
| 原理简单,容易理解和调优 | 需要训练聚类模型(数据量大时训练较慢) |
| 内存占用相对较低 | 聚类边界处的向量可能被漏掉(影响召回率) |
| 适合数据量非常大的场景 | nlist 和 nprobe 的调参需要经验 |
| 支持增量插入(但可能需要定期重新聚类) | 数据分布不均匀时效果下降 |
IVF 还有几个变体:IVF_FLAT 是在簇内做精确搜索,IVF_SQ8 是对簇内向量做量化压缩以节省内存,IVF_PQ 则用乘积量化进一步压缩。后面的选型表格里会对比它们的差异。
3. HNSW(分层可导航小世界图):最主流的索引算法
HNSW 的全称是 Hierarchical Navigable Small World Graph(分层可导航小世界图)。名字很长,但它是目前最主流、效果最好的 ANN 索引算法,几乎所有向量数据库都把它作为默认或推荐的索引类型。
3.1 HNSW 的核心思想:多层图结构
要理解 HNSW,先从一个生活场景开始。
假设你要找一个"住在北京朝阳区、会写 Java、喜欢打篮球"的人,但你手上没有任何名单,只能通过社交关系去找。你会怎么做?
你不会挨个问全中国 14 亿人。你会这样:
- 先在你认识的人里找——“谁在北京?”——你的朋友老王在北京
- 问老王——“你认识朝阳区的人吗?”——老王介绍了他同事小李
- 问小李——“你认识会写 Java 的人吗?”——小李介绍了他的大学同学张三
- 张三恰好也喜欢打篮球——找到了!
每一步你都在靠近目标,而且每一步只需要问几个人,不需要遍历所有人。
HNSW 的思路和这个完全一样,只不过把“人”换成了“向量”,把“社交关系”换成了“图中的边”。
HNSW 的核心结构是一个多层图:
- 最底层(Layer 0)包含所有向量,每个向量和它附近的若干个向量相连
- 往上每一层的向量数量越来越少(随机抽取),但连接的跨度越来越大
- 最顶层只有很少的几个向量,但它们之间的连接覆盖了整个向量空间
检索的时候,从最顶层开始,快速定位到目标的大致区域,然后逐层下降,每一层都在更精细的范围内搜索,最终在最底层找到最相似的向量。

3.2 用一个具体例子走一遍 HNSW 的检索过程
为了让你更直观地理解,咱们用一个简化的例子走一遍。
假设向量数据库里有 8 个向量(A、B、C、D、E、F、G、H),HNSW 建了 3 层图。现在要查询和向量 Q 最相似的向量。
Layer 2(顶层):只有 A 和 E 两个向量
- 从 A 开始,计算 Q 和 A 的距离、Q 和 E 的距离
- 发现 E 离 Q 更近,移动到 E
Layer 1(中间层):有 A、C、E、G 四个向量
- 从 E 出发,看 E 的邻居:C 和 G
- 计算 Q 和 C、Q 和 G 的距离
- 发现 G 离 Q 更近,移动到 G
Layer 0(底层):所有 8 个向量都在
- 从 G 出发,看 G 的邻居:F 和 H
- 计算 Q 和 F、Q 和 H 的距离
- 发现 H 离 Q 最近
- 再看 H 的邻居,没有比 H 更近的了
- 结果:H 是和 Q 最相似的向量
整个过程只计算了 6 次距离(A、E、C、G、F、H),而不是 8 次。数据量小的时候差距不明显,但如果有 100 万个向量,HNSW 通常只需要计算几百到几千次距离就能找到结果。
3.3 为什么 HNSW 这么快
HNSW 快的原因可以归结为两点:
第一,多层结构实现了“粗到细”的搜索。顶层的少量向量帮你快速跳到目标附近,底层的密集连接帮你精确定位。这和跳表(Skip List)的思想很像——如果你了解 Redis 的有序集合(ZSet),它底层用的就是跳表,原理是相通的。
第二,“小世界”特性保证了图的连通性。在 HNSW 的图中,任意两个向量之间只需要经过很少的“跳转” 就能到达(类似“六度分隔理论”——你和世界上任何一个人之间最多只隔 6 个人)。这意味着搜索不会陷入死角,总能快速逼近目标。
3.4 HNSW 的代价:内存占用
HNSW 的检索速度和精度都很优秀,但它有一个明显的代价:内存占用大。
因为 HNSW 需要在内存中维护整个图结构——不仅要存所有向量本身,还要存向量之间的连接关系(边)。每个向量在每一层都有若干条边,这些边的存储开销不小。
具体来说,HNSW 有两个关键参数影响内存和性能:
| 参数 | 含义 | 调大的效果 | 调小的效果 |
|---|---|---|---|
| M | 每个向量在每层的最大连接数 | 召回率更高,但内存占用更大,建索引更慢 | 内存省,但召回率可能下降 |
| efConstruction | 建索引时的搜索宽度 | 索引质量更高(连接更合理),但建索引更慢 | 建索引快,但索引质量可能下降 |
检索时还有一个参数 ef(搜索宽度),控制检索时探索的候选集大小。ef 越大,召回率越高,但检索越慢。
一个粗略的估算:100 万个 4096 维向量,用 HNSW 索引(M=16),大约需要 16~20 GB 内存。如果你的服务器内存有限,可能需要考虑 IVF 系列索引,它们的内存占用要小得多。
4. 索引算法对比:怎么选
Milvus 支持多种索引类型,下面是最常用的几种对比:
| 索引类型 | 核心思想 | 检索速度 | 召回率 | 内存占用 | 适用数据量 | 适用场景 |
|---|---|---|---|---|---|---|
| FLAT | 暴力搜索,不建索引 | 最慢 | 100%(精确) | 低(只存原始向量) | < 10 万 | 对精度要求极高,数据量小 |
| IVF_FLAT | 聚类分区 + 簇内精确搜索 | 快 | 95%~99% | 较低 | 百万~千万 | 数据量大,内存有限 |
| IVF_SQ8 | 聚类分区 + 标量量化压缩 | 快 | 93%~97% | 低(向量压缩为 1/4) | 千万~亿级 | 数据量很大,愿意牺牲一点精度换内存 |
| HNSW | 多层图结构 | 最快 | 97%~99.5% | 高(需存图结构) | 百万~千万 | 对速度和精度都有要求,内存充足 |
| DISKANN | 基于磁盘的图索引 | 较快 | 95%~98% | 低(索引在磁盘) | 亿级 | 数据量极大,内存不够放 HNSW |
怎么选?一个简单的决策路径:
- 数据量 < 10 万,直接用 FLAT,暴力搜索就够了
- 数据量 10 万~500 万,内存充足 → HNSW;内存有限 → IVF_FLAT
- 数据量 500 万~5000 万,HNSW 如果内存放得下就用 HNSW,放不下用 IVF_SQ8
- 数据量 > 5000 万,考虑 DISKANN 或 IVF_PQ
对于大多数 RAG 项目来说,数据量在百万级别,HNSW 是最优选择。这也是为什么后面的实战代码里我们用 HNSW 作为索引类型。
主流向量数据库对比与选型
理解了向量检索的算法原理之后,下一个问题是:用哪个向量数据库?
市面上的向量数据库方案大致分两类。
1. 向量数据库的分类
1.1 专用向量数据库
从零开始为向量检索设计的数据库,向量是一等公民。代表产品:Milvus、Qdrant、Weaviate、Pinecone、Chroma。
它们的特点是:原生支持多种 ANN 索引算法,针对向量检索做了大量底层优化(内存管理、并行计算、索引构建等),通常还支持标量过滤(在向量检索的同时按元数据条件过滤)。
1.2 传统数据库的向量扩展
在已有的关系型数据库上加一个向量检索插件。代表产品:pgvector(PostgreSQL 的扩展)、MySQL 8.0+ 的向量支持、Elasticsearch 的 kNN 搜索。
它们的优势是不用引入新的基础设施——如果你的项目已经在用 PostgreSQL,加一个 pgvector 扩展就能存向量、做检索,运维成本低。但在大数据量下的检索性能、索引类型的丰富度、以及向量检索的专项优化上,和专用向量数据库还是有差距。
1.3 怎么选
一个简单的判断标准:
- 如果你的向量数据量 < 50 万,且项目已经在用 PostgreSQL → pgvector 够用,省事
- 如果向量数据量 > 50 万,或者对检索性能有较高要求 → 用专用向量数据库
- 如果是学习和原型验证阶段 → Chroma(轻量,Python 生态好)或 Milvus(功能全,Java SDK 完善)
2. 主流方案对比
| 数据库 | 类型 | 部署方式 | 适用数据量 | 语言 SDK | 索引类型 | 标量过滤 | 开源 | 适用场景 |
|---|---|---|---|---|---|---|---|---|
| Milvus | 专用 | 自部署(Docker/K8s)或 Zilliz Cloud | 百万~十亿级 | Java、Python、Go、Node.js | HNSW、IVF 系列、DISKANN 等 | 支持 | 是(Apache 2.0) | 大规模生产环境,Java 技术栈 |
| Qdrant | 专用 | 自部署(Docker)或 Qdrant Cloud | 百万~亿级 | Python、Rust、Go、Java | HNSW | 支持 | 是(Apache 2.0) | Rust 生态,高性能单机场景 |
| Weaviate | 专用 | 自部署(Docker)或 Weaviate Cloud | 百万~千万级 | Python、Go、Java、JS | HNSW | 支持 | 是(BSD-3) | 内置向量化能力,全托管偏好 |
| Pinecone | 专用 | 纯云托管(无自部署) | 百万~亿级 | Python、Node.js | 自研 | 支持 | 否 | 不想运维,纯云方案 |
| Chroma | 专用 | 嵌入式 / Docker | < 百万 | Python、JS | HNSW | 支持 | 是(Apache 2.0) | 原型验证,轻量场景 |
| pgvector | 扩展 | 随 PostgreSQL 部署 | < 百万 | 所有支持 PG 的语言 | HNSW、IVF_FLAT | 支持(SQL WHERE) | 是 | 已有 PG,数据量不大 |
3. 为什么选 Milvus
本系列选择 Milvus 作为向量数据库,原因有几个:
- 开源且社区活跃:Apache 2.0 协议,截止 26.2.20 号 GitHub 上 42.8k+ star,文档和社区资源丰富
- Java SDK 完善:本系列的代码示例用 Java,Milvus 的 Java SDK(
io.milvus:milvus-sdk-java)功能完整,API 设计清晰 - 支持大规模数据:从几万到几十亿向量都能应对,单机模式适合开发和中小规模,集群模式适合大规模生产
- 索引类型丰富:HNSW、IVF_FLAT、IVF_SQ8、DISKANN 等都支持,可以根据场景灵活选择
- 标量过滤能力强:支持在向量检索的同时按元数据字段过滤(比如:只在退货政策类的 chunk 里检索),这在 RAG 场景中非常实用
- 本地部署简单:一个
docker compose up -d就能启动,开发环境零门槛
需要说明的是,选 Milvus 不代表它在所有场景下都是最优解。如果你的项目是 Python 技术栈且数据量不大,Chroma 可能更轻便;如果你不想自己运维,Pinecone 的全托管方案也值得考虑。技术选型没有银弹,适合你的场景才是最好的。
Milvus 核心概念:和传统数据库做类比
在动手写代码之前,先搞清楚 Milvus 里的几个核心概念。如果你用过 MySQL,理解起来会很快——Milvus 的概念体系和关系型数据库有很多对应关系。
1. Collection = 表
Collection 是 Milvus 中数据组织的基本单位,对应 MySQL 中的表(Table)。
一个 Collection 存储一类向量数据。比如在我们的电商客服知识库场景中,可以创建一个名为 customer_service_chunks 的 Collection,里面存所有客服知识库的 chunk 向量。
如果你有多个业务场景(比如客服知识库、商品搜索、内容推荐),通常每个场景创建一个独立的 Collection。
2. Schema = 表结构
Schema 定义了 Collection 中每条数据包含哪些字段,对应 MySQL 中的表结构(CREATE TABLE 时定义的列)。
一个典型的 RAG 场景的 Schema 包含三类字段:
| 字段类型 | 示例 | 说明 |
|---|---|---|
| 主键字段 | id(Int64 或 VarChar) | 每条数据的唯一标识,类似 MySQL 的主键 |
| 向量字段 | vector(FloatVector) | 存储 Embedding 向量,需要指定维度 |
| 标量字段 | chunk_text、doc_id、category 等 | 存储元数据,用于过滤和展示 |
2.1 向量字段和标量字段的区别
这里要特别说明一下向量字段和标量字段的区别,因为这是 Milvus 和传统数据库最大的不同。
标量字段存储的是普通数据(字符串、数字、布尔值等),和 MySQL 的列没什么区别。你可以对标量字段建索引、做等值查询、范围查询、模糊匹配等。
向量字段存储的是高维浮点数数组(比如 4096 维的 float 数组),它不能做等值查询(两个向量完全相等的概率几乎为零),只能做相似度检索(找最近的 Top-K 个)。向量字段需要建专门的向量索引(HNSW、IVF 等),这和标量字段的 B+ 树索引是完全不同的东西。
3. Index = 索引
Milvus 中的索引分两种:
- 向量索引:为向量字段创建的 ANN 索引(HNSW、IVF_FLAT 等),用于加速向量相似度检索。这是 Milvus 的核心能力。
- 标量索引:为标量字段创建的索引,用于加速过滤条件的执行。类似 MySQL 的 B+ 树索引。
在 RAG 场景中,通常需要同时用到两种索引:向量索引用于找到语义最相似的 chunk,标量索引用于按元数据过滤(比如只搜索某个类别的 chunk)。