摘要
本文带你从零开始,系统掌握向量数据库Milvus的核心概念、索引算法选型、倒排索引底层原理,并串联起RAG离线/在线完整技术链路。文章采用"人话"类比 + 代码注释 + 实战调参三位一体的方式,让你真正融会贯通,而非死记硬背。
关键词:Milvus、向量数据库、RAG、HNSW、IVF、倒排索引、稠密向量、稀疏向量、混合检索
一、为什么需要向量数据库?------从RAG说起
1.1 RAG是什么?
RAG(检索增强生成)是当下最火的大模型应用范式。它的核心思想很简单:让AI在回答问题前,先"翻书"找答案,再根据找到的内容进行回答。
text
┌─────────────────────────────────────────────────────────────┐
│ RAG 核心流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户问题 ──→ 向量检索 ──→ 召回相关文档 ──→ LLM生成 │
│ ↑ ↑ ↑ ↑ │
│ Embedding Milvus 知识库 GPT-4 │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 为什么需要专门的向量数据库?
传统数据库(MySQL)无法高效处理向量检索,因为:
- 向量是高维浮点数数组(如768维),无法用B+树索引
- 检索方式是"语义相似"而非"精确匹配",需要计算向量间距离
- 数据量巨大(百万/亿级),需要专用索引加速
这正是 Milvus 等向量数据库的价值所在。
二、Milvus核心概念:从SQL视角理解
2.1 概念类比表
| SQL概念 | Elasticsearch | Milvus | 通俗解释 |
|---|---|---|---|
| Table(表) | Index(索引) | Collection(集合) | 一张Excel工作表 |
| DDL(建表语句) | Mapping(映射) | Schema(模式) | 设计表头+设置单元格格式 |
| Row(行) | Document(文档) | Entity(实体) | 表格里的一行数据 |
| Column(列) | Field(字段) | FieldSchema(字段定义) | 表格里的一列 |
2.2 Schema定义(带详细注释)
python
from pymilvus import CollectionSchema, FieldSchema, DataType
# ============================================================
# 1. 定义每个字段(就像在Excel里设计每一列)
# ============================================================
# 主键字段:相当于"身份证号",唯一且不能为空
book_id = FieldSchema(
name="book_id",
dtype=DataType.INT64,
is_primary=True, # 设为主键
auto_id=False, # 是否自动生成ID
)
# 标量字段:存储元数据(肉眼看的)
book_name = FieldSchema(
name="book_name",
dtype=DataType.VARCHAR,
max_length=200, # VARCHAR必须指定最大长度!
description="书名"
)
# 标量字段:用于过滤查询
word_count = FieldSchema(
name="word_count",
dtype=DataType.INT64,
)
# 稠密向量字段:存储"语义指纹"(AI看的)
book_intro = FieldSchema(
name="book_intro",
dtype=DataType.FLOAT_VECTOR,
dim=128, # 向量维度,必须和Embedding模型一致!
)
# 稀疏向量字段:存储"关键词权重"
book_keywords = FieldSchema(
name="book_keywords",
dtype=DataType.SPARSE_FLOAT_VECTOR, # 稀疏向量类型
)
# ============================================================
# 2. 组装成Collection Schema(组装成完整的表结构)
# ============================================================
schema = CollectionSchema(
fields=[book_id, book_name, word_count, book_intro, book_keywords],
description="图书检索集合",
enable_dynamic_field=True, # 是否允许动态字段(类似MongoDB随意存)
)
# ============================================================
# 3. 创建Collection(真正建表)
# ============================================================
from pymilvus import Collection
collection = Collection(
name="book",
schema=schema,
using='default', # 指定连接的Milvus服务
)
⚠️ 重要提醒:Schema的"刚性"
在Milvus 2.x版本中,Schema一旦创建就几乎不可修改:
- ❌ 不能添加新字段
- ❌ 不能删除字段
- ❌ 不能修改字段类型
- ❌ 不能修改向量维度
本质 :这里的DDL就像"刻在花岗岩上的兵马俑坑道",必须极其慎重。而在MySQL中,你可以随时ALTER TABLE加列------这是两者DDL的最大区别。
三、理解向量:稠密 vs 稀疏
3.1 稠密向量(Dense Vector)
| 特性 | 说明 |
|---|---|
| 定义 | 大部分维度都非零的高维向量 |
| 维度 | 固定,如768、1536 |
| 来源 | BERT、OpenAI embedding模型 |
| 擅长 | 语义理解(同义词、上下文) |
| 类比 | 印象派油画------传达整体意境 |
| 典型模型 | text-embedding-ada-002、BGE |
python
# 稠密向量示例(768维,每个值都是浮点数)
dense_vector = [
0.123, -0.456, 0.789, # 几乎所有维度都有非零值
0.234, -0.567, 0.890,
# ... 共768个浮点数
]
3.2 稀疏向量(Sparse Vector)
| 特性 | 说明 |
|---|---|
| 定义 | 绝大部分维度为零的高维向量 |
| 维度 | 极高(数万),但非零值极少 |
| 来源 | SPLADE、BGE-M3、BM25算法 |
| 擅长 | 精确关键词匹配(专有名词) |
| 类比 | 精确的购物清单------只列出具体物品 |
| 典型模型 | SPLADEv2、BGE-M3 |
python
# 稀疏向量示例(只记录非零的维度索引和值)
sparse_vector = {
1234: 0.8, # 维度1234的值为0.8(如"量子"这个词的权重)
5678: 0.9, # 维度5678的值为0.9(如"纠缠"这个词的权重)
# 其他9998个维度都是0,不记录
}
# 在Milvus中通常表示为 [(1234, 0.8), (5678, 0.9)]
3.3 为什么用两种向量?
| 场景 | 只用稠密 | 只用稀疏 | 混合检索 |
|---|---|---|---|
| 搜索"苹果" | 能找到"水果"相关 | 只能找到含"苹果"的 | ✅ 两者兼顾 |
| 搜索"iPhone 15" | 可能找不到精确型号 | 精确匹配型号 | ✅ 精准+语义 |
| 搜索"人工智能发展趋势" | ✅ 语义理解好 | 只能匹配关键词 | ✅ 效果最佳 |
四、索引算法:IVF vs HNSW
4.1 前置知识:KNN vs ANN
┌─────────────────────────────────────────────────────────────────┐
│ KNN vs ANN │
├─────────────────────────────────────────────────────────────────┤
│ │
│ KNN (K-最近邻):精确但极慢 │
│ ├── 暴力搜索:翻遍图书馆每一本书 │
│ ├── 计算复杂度:O(N) │
│ └── 优点:100%精确 | 缺点:1亿数据无法接受 │
│ │
│ ANN (近似最近邻):飞快且足够准 │
│ ├── 近似搜索:用地圖导航,只搜最近的分区 │
│ ├── 计算复杂度:O(log N) 或 O(1) │
│ └── 优点:毫秒级响应 | 缺点:召回率95~99%(用户无感知) │
│ │
│ 💡 结论:IVF、HNSW、倒排索引 全都属于 ANN 家族! │
│ │
└─────────────────────────────────────────────────────────────────┘
4.2 IVF(倒排文件索引)------"先分类再翻找"
核心思想:分而治之。建索引时用K-means聚类,查询时只搜最近的几个分区。
| 参数 | 含义 | 何时设置 | 调优建议 |
|---|---|---|---|
nlist |
分区总数(把数据分成多少堆) | 建索引时固定 | 4 * sqrt(n),n为数据量 |
nprobe |
查询时探测几个分区 | 查询时动态调整 | 从1开始,逐步增大找平衡 |
python
# ============================================================
# IVF 索引创建与查询(带详细注释)
# ============================================================
import math
from pymilvus import Collection
# ----- 1. 建索引时:设置 nlist -----
num_entities = 1000000 # 100万条数据
nlist = int(4 * math.sqrt(num_entities)) # 经验公式:≈ 4000
index_params = {
"metric_type": "L2", # L2欧氏距离 或 IP内积
"index_type": "IVF_FLAT", # 索引类型
"params": {"nlist": nlist} # 【核心】分区总数
}
collection.create_index(
field_name="embedding",
index_params=index_params
)
# ----- 2. 查询时:设置 nprobe -----
collection.load() # 必须加载到内存才能搜索
search_params = {
"metric_type": "L2",
"params": {"nprobe": 8} # 【核心】只搜8个分区
# 调优:从 1 → 8 → 16 → 32 逐步测试速度和召回率
}
results = collection.search(
data=[query_vector],
anns_field="embedding",
param=search_params,
limit=10,
)
4.3 HNSW(分层可导航小世界)------"立体高架路网"
核心思想:构建多层图结构,自上而下导航到目标。
| 参数 | 含义 | 调优建议 |
|---|---|---|
M |
每个节点的最大连接数 | 16(平衡)或32(更准但吃内存) |
efConstruction |
建索引时的搜索宽度 | M的2~4倍,如M=16时设200 |
ef |
查询时的搜索宽度 | ≥ TopK,设为TopK的3~10倍 |
python
# ============================================================
# HNSW 索引创建与查询(带详细注释)
# ============================================================
# ----- 1. 建索引时:设置 M 和 efConstruction -----
index_params = {
"metric_type": "L2",
"index_type": "HNSW",
"params": {
"M": 16, # 【核心】朋友圈人数上限
"efConstruction": 200 # 【核心】建路时的勘察深度
}
}
collection.create_index(field_name="embedding", index_params=index_params)
# ----- 2. 查询时:设置 ef -----
search_params = {
"metric_type": "L2",
"params": {
"ef": 64 # 【核心】查询时的探头范围
}
}
results = collection.search(
data=[query_vector],
anns_field="embedding",
param=search_params,
limit=10,
)
4.4 IVF vs HNSW:选型决策表
| 场景 | 推荐索引 | 理由 |
|---|---|---|
| 数据量极大(千万/亿级),内存有限 | IVF_FLAT / IVF_SQ8 | HNSW内存会爆,IVF靠分区省内存 |
| 追求极速响应(毫秒级),内存充足 | HNSW | 延迟最低最稳定 |
| 数据频繁增删改(动态性高) | IVF | HNSW是静态图,动态插入重建代价大 |
| 追求极致召回率(99%以上) | HNSW(调大M和ef) | 图结构能找到极深邻居 |
五、深入理解"倒排"概念
5.1 两个"倒排"的区别(最容易混淆的知识点!)
┌─────────────────────────────────────────────────────────────────┐
│ "倒排"的两层含义 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ IVF的"Inverted File"(倒排文件) │
│ ├── 关系:聚类中心 → 向量ID列表 │
│ ├── 目的:缩小搜索包围圈(空间分区) │
│ ├── 类比:仓库分区表("3号货架放了A/B/C") │
│ └── 服务对象:稠密向量 │
│ │
│ 2️⃣ ES/稀疏向量的"Inverted Index"(倒排索引) │
│ ├── 关系:关键词 → 文档ID列表 │
│ ├── 目的:精确命中关键词(文本匹配) │
│ ├── 类比:拼音字典("苹果"出现在第1/3/5页) │
│ └── 服务对象:文本/稀疏向量 │
│ │
│ 💡 核心区别:IVF反的是"坐标↔住户";ES反的是"书↔词语" │
│ │
└─────────────────────────────────────────────────────────────────┘
5.2 稀疏向量专用索引:SPARSE_INVERTED_INDEX
python
# ============================================================
# 稀疏向量索引创建
# ============================================================
index_params = {
"metric_type": "IP", # 稀疏向量通常用内积
"index_type": "SPARSE_INVERTED_INDEX", # 【核心】专用倒排索引
"params": {
"drop_ratio_build": 0.1, # 建索引时丢弃低权重维度
}
}
collection.create_index(
field_name="book_keywords", # 稀疏向量字段
index_params=index_params
)
5.3 Lucene三级数据结构(ES和Milvus稀疏索引的底层)
┌─────────────────────────────────────────────────────────────────┐
│ Lucene 倒排索引三层结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 顶层:词项索引 (Term Index) │
│ ├── 技术:FST(有限状态转换器) │
│ ├── 作用:快速定位词汇前缀所在位置 │
│ └── 类比:图书馆大门的"区域指示牌"(省内存) │
│ │
│ 中间层:词项字典 (Term Dictionary) │
│ ├── 技术:按字典序排序 + 二分查找 │
│ ├── 作用:精确定位具体词汇 │
│ └── 类比:二楼房间的"精确书架标签" │
│ │
│ 底层:倒排列表 (Posting List) │
│ ├── 技术:FOR(差值编码)压缩 │
│ ├── 作用:存储具体文档ID列表 │
│ └── 类比:书背后的"藏身地点清单"(占硬盘最大) │
│ │
└─────────────────────────────────────────────────────────────────┘
压缩与加速技术:
text
📦 FOR (Frame of Reference):差值编码压缩
原始: [102, 105, 108, 200, 205]
压缩: [102, +3, +3, +92, +5] → 节省70%空间
🚀 Roaring Bitmaps:位图加速交集运算
用0/1位图代替数字列表,CPU位运算极快
⏭️ Skip List:跳表加速合并
在倒排列表上建"快捷方式",跳过不匹配项
六、RAG完整技术链路
6.1 离线阶段:数据工程与索引构建
text
┌─────────────────────────────────────────────────────────────────┐
│ RAG 离线阶段(数据准备) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 原始文档(PDF/Word/网页) │
│ ↓ │
│ 数据清洗与分块(Chunking) │
│ └── 按句子/语义切分,每块500~1000 token │
│ ↓ │
│ 向量化(Embedding) │
│ ├── 稠密向量:BGE/OpenAI → 768/1536维浮点数 │
│ └── 稀疏向量:BGE-M3/SPLADE → 数万维关键词权重 │
│ ↓ │
│ Schema定义(DDL) │
│ └── 钉死维度!建表后不可改! │
│ ↓ │
│ 索引构建(Indexing) │
│ ├── 稠密向量:IVF(nlist)或 HNSW(M, efConstruction) │
│ └── 稀疏向量:SPARSE_INVERTED_INDEX(FST+FOR压缩) │
│ ↓ │
│ 数据持久化 → Milvus磁盘存储 │
│ │
└─────────────────────────────────────────────────────────────────┘
6.2 在线阶段:查询与生成
text
┌─────────────────────────────────────────────────────────────────┐
│ RAG 在线阶段(检索+生成) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户问题:"什么是量子纠缠?" │
│ ↓ │
│ 查询向量化(同款Embedding模型) │
│ ├── 稠密查询向量 │
│ └── 稀疏查询向量(关键词权重) │
│ ↓ │
│ Milvus 混合检索(Hybrid Search) │
│ ├── 稠密路径(语义):IVF→nprobe分区 或 HNSW→ef搜索 │
│ ├── 稀疏路径(关键词):查倒排索引(FST定位+FOR解压) │
│ └── 融合:RRF算法合并两条路径结果 │
│ ↓ │
│ 元数据过滤(标量筛选) │
│ └── "只要2024年发表的论文"(前过滤/后过滤) │
│ ↓ │
│ 重排序(Rerank,可选) │
│ └── Cross-Encoder精排,挑出Top 5 │
│ ↓ │
│ Prompt构建 + LLM生成 │
│ └── "基于以下参考资料回答问题:..." → GPT-4 │
│ ↓ │
│ 最终答案返回给用户 │
│ │
└─────────────────────────────────────────────────────────────────┘
6.3 混合检索代码示例
python
from pymilvus import Collection
# ============================================================
# 混合检索:稠密+稀疏同时搜索,RRF融合
# ============================================================
# 1. 稠密向量搜索(语义路径)
dense_search_params = {
"metric_type": "IP",
"params": {"nprobe": 8} # IVF参数
}
dense_results = collection.search(
data=[dense_query_vector], # 768维稠密向量
anns_field="book_intro", # 稠密向量字段
param=dense_search_params,
limit=100, # 各取100条
)
# 2. 稀疏向量搜索(关键词路径)
sparse_search_params = {
"metric_type": "IP",
"params": {} # 稀疏向量专用参数
}
sparse_results = collection.search(
data=[sparse_query_vector], # 稀疏向量
anns_field="book_keywords", # 稀疏向量字段
param=sparse_search_params,
limit=100,
)
# 3. RRF融合(Reciprocal Rank Fusion)
# Milvus 2.4+ 支持内置混合搜索,此处展示逻辑
def rrf_fusion(results_list, k=60):
"""倒数排名融合算法"""
scores = {}
for results in results_list:
for rank, hit in enumerate(results):
doc_id = hit.id
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)[:10]
6.4 前过滤 vs 后过滤
python
# ============================================================
# 带标量过滤的向量搜索
# ============================================================
# Milvus会自动选择前过滤或后过滤策略
# 你只需要写 filter 表达式即可
search_res = client.search(
collection_name="book",
data=[query_vector],
filter='word_count > 5000 and book_name like "%AI%"', # 【核心】过滤条件
limit=10,
output_fields=["book_name", "word_count"]
)
# 💡 性能优化:
# 1. 为高频过滤字段创建标量索引
index_params.add_index(field_name="word_count", index_type="STL_SORT")
# 2. 使用Partition Key做物理隔离
FieldSchema(name="tenant_id", dtype=DataType.VARCHAR, is_partition_key=True)
七、Elasticsearch vs Milvus:选型对比
7.1 核心差异
| 对比维度 | Elasticsearch | Milvus |
|---|---|---|
| 本质 | 通用搜索引擎 + 向量插件 | 专业向量数据库 |
| 设计基因 | 全文搜索出身 | 向量检索出身 |
| 索引算法 | HNSW(基于Lucene分段) | IVF、HNSW、DiskANN等 |
| 内存效率 | 高,HNSW全部在内存 | 中/低,支持磁盘索引 |
| 扩展性 | 分片集群,有瓶颈 | 原生分布式,弹性伸缩 |
| 混合搜索 | ✅ 原生支持BM25+kNN | ✅ 支持稠密+稀疏+RRF |
| 适用规模 | 千万级 | 十亿级 |
7.2 选型决策
text
┌─────────────────────────────────────────────────────────────────┐
│ 选型决策流程图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 你的需求是什么? │
│ │ │
│ ├── 需要全文搜索 + 日志分析 + 适量向量 → 【ES】 │
│ │ │
│ ├── 大规模RAG / 推荐系统 / AI Agent → 【Milvus】 │
│ │ │
│ ├── 图像/视频/音频检索 → 【Milvus】 │
│ │ │
│ └── 多模态搜索(文本+图像)→ 【Milvus】(支持10个向量字段)│
│ │
│ 💡 常见演进路径:先用ES快速验证 → 数据量增长后迁移到Milvus │
│ │
└─────────────────────────────────────────────────────────────────┘
八、核心知识点速查表
| 知识点 | 一句话总结 | 关键参数/技术 |
|---|---|---|
| Collection | 一张Excel表 | Collection(name, schema) |
| Schema | 刻在花岗岩上的表结构 | FieldSchema(dtype, dim) |
| 稠密向量 | 语义指纹(印象派油画) | 768/1536维,全部非零 |
| 稀疏向量 | 关键词权重(购物清单) | 数万维,极少非零 |
| IVF | 先分区再搜索 | nlist(建索引),nprobe(查) |
| HNSW | 立体高架路网导航 | M,efConstruction,ef |
| 倒排文件(IVF) | 聚类中心→向量ID | 空间分区用 |
| 倒排索引(ES) | 关键词→文档ID | FST+FOR+Roaring Bitmap |
| 混合检索 | 稠密+稀疏+RRF融合 | HybridSearch |
| 前过滤 | 先筛选再搜索 | 自动优化,写filter即可 |
| KNN | 精确但极慢(O(N)) | 暴力搜索 |
| ANN | 近似但飞快 | IVF/HNSW/倒排索引都算 |
| RAG离线 | 建知识库+索引 | 分块→Embedding→建索引 |
| RAG在线 | 检索+LLM生成 | 混合检索→Rerank→Prompt |
九、灵魂追问(检验你是否真的懂了)
-
为什么Milvus的Schema一旦创建就几乎不能改?
因为向量维度必须固定,GPU/CPU才能做并行计算。类比:让1000个工人搬同样长度的筷子才能流水线作业。
-
IVF的"倒排文件"和ES的"倒排索引"有什么区别?
IVF反的是"数据↔物理存储位置"(空间分区);ES反的是"文档↔词汇"(文本匹配)。一个搞地理,一个搞语文。
-
为什么稀疏向量不能用IVF分区?
稀疏向量绝大多数维度为0,计算欧氏距离时所有点距离都差不多,聚不出有意义的类。而且99.9%的计算是0乘以0,资源浪费。
-
KNN和ANN是什么关系?
KNN是精确的数学标准答案(翻遍全书);ANN是聪明的工程实现(用索引导航)。IVF、HNSW都属于ANN家族。
-
什么时候用ES,什么时候用Milvus?
要全文搜索+向量→ES;要大规模专用向量检索→Milvus。常见路径:ES快速验证 → 迁移到Milvus。
十、写在最后
本文从RAG场景切入,系统覆盖了向量数据库的核心概念、数据类型、索引算法、底层原理和完整技术链路。希望你在阅读后能够:
- 分清:Collection vs Schema,稠密 vs 稀疏,IVF vs HNSW,两个"倒排"
- 理解:为什么Schema刚性、为什么稀疏向量用倒排索引、KNN vs ANN
- 会用:IVF/HNSW参数调优、混合检索、前/后过滤
- 选型:明确ES和Milvus各自的适用场景
向量数据库是AI时代的核心基础设施,掌握它就是掌握了通往大模型应用的金钥匙。
📌 附言 :本文所有代码示例基于
pymilvus和 Milvus 2.4+ 版本。实际使用时请参考官方文档获取最新API。如有疑问,欢迎留言交流!