【向量数据库】Milvus 从入门到精通:索引、检索、混合搜索一篇打通(RAG 必备)
💡 写在前面 :做 RAG、做推荐、做语义搜索,绕不开"向量数据库"这四个字。市面上的教程要么只讲 API 调用,要么只讲理论算法,真正能把部署 → Schema 设计 → 索引选型 → 搜索 → 混合检索 → 多模态一条龙讲透的少之又少。
这篇文章以 Milvus 为主线,把向量存储的核心知识系统梳理一遍,并配套 20 道高频面试题。无论你是刚入门 RAG,还是准备面试,都能直接收藏当手册用。📖
📑 文章目录
- 一、先搞清楚:为什么需要向量数据库?
- [二、Milvus 部署与连接](#二、Milvus 部署与连接)
- [三、Collection / Partition / Alias 三剑客](#三、Collection / Partition / Alias 三剑客)
- [四、Schema 设计:字段类型全家桶](#四、Schema 设计:字段类型全家桶)
- [五、🔑 核心:五大索引类型怎么选?](#五、🔑 核心:五大索引类型怎么选?)
- [六、搜索全家桶:从 ANN 到迭代搜索](#六、搜索全家桶:从 ANN 到迭代搜索)
- 七、LlamaIndex:更高层的抽象
- [八、🔥 重头戏:混合检索(Dense + Sparse)](#八、🔥 重头戏:混合检索(Dense + Sparse))
- [九、BGE-M3 与 ColBERT:延迟交互到底牛在哪?](#九、BGE-M3 与 ColBERT:延迟交互到底牛在哪?)
- 十、多模态检索:图文跨模态怎么玩
- [十一、🎯 速查表合集](#十一、🎯 速查表合集)
- [十二、20 道高频面试题(建议背诵)](#十二、20 道高频面试题(建议背诵))
- 十三、总结
一、先搞清楚:为什么需要向量数据库?
很多新手会问:MySQL 不是已经能存数据了吗,为什么还要专门搞一个"向量数据库"?
答案的核心在于 "相似度检索" 这件事传统数据库做不了:
| 对比维度 | 关系型数据库(MySQL) | 向量数据库(Milvus) |
|---|---|---|
| 存储对象 | 结构化标量数据(数字、字符串) | 高维向量 + 标量元数据 |
| 查询方式 | 精确匹配(WHERE、JOIN) |
相似度搜索(ANN 近似最近邻) |
| 索引类型 | B+Tree、Hash | FLAT、IVF_FLAT、HNSW 等 |
| 距离度量 | 无 | L2、IP、COSINE |
| 典型场景 | 事务处理、业务 CRUD | 语义搜索、推荐系统、RAG |
🎯 一句话总结 :向量数据库解决的是高维空间中的相似度检索问题。传统数据库找的是"等于",向量数据库找的是"像不像"。
KNN vs ANN:为什么不总是精确搜索?
- KNN(精确搜索) :遍历所有向量找真正的 Top-K,时间复杂度
O(n) - ANN(近似最近邻) :通过索引结构在亚线性时间内找到近似 Top-K
实际场景中,向量本身就是模型的近似表示,追求 100% 精确匹配意义不大,95%+ 的召回率通常已经完全够用,但速度能提升好几个数量级。
二、Milvus 部署与连接
Milvus Standalone 通过 Docker Compose 部署,包含三个核心服务:
- etcd:元数据存储(端口 2379)
- minio:对象存储(端口 9000/9001)
- milvus-standalone:向量数据库服务(端口 19530/9091)
yaml
# docker-compose.yml 核心配置
image: milvusdb/milvus:v2.5.10
ports:
- "19530:19530" # gRPC API
- "9091:9091" # 健康检查
两种连接方式
python
from pymilvus import MilvusClient
# 方式一:连接远程服务(生产环境)
client = MilvusClient(uri="http://localhost:19530", token="root:Milvus")
# 方式二:连接本地文件数据库(Milvus Lite,适合开发调试)
client = MilvusClient(uri="./wukong.db")
数据库级隔离
Milvus 支持多数据库隔离,概念类似 MySQL 里的"数据库":
python
# 创建数据库(可设置副本数)
client.create_database(db_name="my_database", properties={"database.replica.number": 3})
# 增删改查一套带走
client.list_databases()
client.describe_database(db_name="default")
client.use_database(db_name="my_database")
client.drop_database(db_name="my_database")
三、Collection / Partition / Alias 三剑客
这三个概念是 Milvus 数据组织的核心,面试必问!
1. Collection(集合)------ 类比"表"
集合是存储数据的基本单元。
python
# 快速创建(自动生成 Schema)
client.create_collection(collection_name="quick_setup", dimension=5)
完整管理操作一览:
| 操作 | 方法 | 说明 |
|---|---|---|
| 列出集合 | list_collections() |
获取所有集合名 |
| 查看详情 | describe_collection() |
获取 Schema 和属性 |
| 重命名 | rename_collection() |
修改集合名称 |
| 设置属性 | alter_collection_properties() |
如设置 TTL |
| 加载/释放 | load_collection() / release_collection() |
控制内存中的数据 |
| 删除 | drop_collection() |
永久删除集合 |
2. Partition(分区)------ 物理隔离加速查询
python
client.create_partition(collection_name="my_col", partition_name="partA")
client.load_partitions(collection_name="my_col", partition_names=["partA"])
client.drop_partition(collection_name="my_col", partition_name="partA")
💡 典型场景:按时间(年/月)或地区分区,查询时只加载相关分区,大幅减少扫描量。
3. Alias(别名)------ 零停机切换神器
python
client.create_alias(collection_name="my_col", alias="alias_v1")
client.alter_alias(collection_name="my_col_v2", alias="alias_v1") # 切换指向
client.drop_alias(alias="alias_v1")
🚀 Alias 最经典场景 :重建索引时,新建
collection_v2→ 建好索引 →alter_alias切换指向 → 用户完全无感知地完成了迁移。
实体 CRUD
python
# 插入
client.insert(collection_name="my_col", data=[{"id": 1, "vector": [...], "color": "red"}])
# upsert:存在则更新,不存在则插入
client.upsert(collection_name="my_col", data=[{"id": 1, "vector": [...]}])
# 删除
client.delete(collection_name="my_col", ids=[0])
# 查询
client.query(collection_name="my_col", filter="id in [1,2]", output_fields=["id", "color"])
四、Schema 设计:字段类型全家桶
Schema 定义了集合的字段结构,Milvus 支持非常丰富的数据类型:
| 类别 | 数据类型 | 说明 |
|---|---|---|
| 主键 | INT64 / VARCHAR |
支持 auto_id 自动生成 |
| 向量 | FLOAT_VECTOR |
32位浮点密集向量 |
BINARY_VECTOR |
二进制向量(维度须为8的倍数) | |
SPARSE_FLOAT_VECTOR |
稀疏浮点向量(用于关键词检索) | |
| 标量 | VARCHAR |
字符串,需指定 max_length |
INT32 / INT64 |
整数 | |
BOOL |
布尔值 | |
JSON |
JSON 对象 | |
ARRAY |
数组,需指定 element_type 和 max_capacity |
实战 Schema 示例
python
from pymilvus import MilvusClient, DataType
schema = MilvusClient.create_schema(auto_id=False, enable_dynamic_field=True)
# 主键
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
# 密集向量
schema.add_field(field_name="text_vector", datatype=DataType.FLOAT_VECTOR, dim=768)
# 稀疏向量(用于 BM25 全文检索)
schema.add_field(field_name="sparse", datatype=DataType.SPARSE_FLOAT_VECTOR)
# 标量字段
schema.add_field(field_name="title", datatype=DataType.VARCHAR, max_length=200,
is_nullable=True, default_value="untitled")
schema.add_field(field_name="metadata", datatype=DataType.JSON)
schema.add_field(field_name="tags", datatype=DataType.ARRAY,
element_type=DataType.VARCHAR, max_capacity=10, max_length=50)
🌟 Milvus 2.5+ 亮点:内置 BM25 函数
可以自动从文本生成稀疏向量,无需手动调 Embedding:
python
from pymilvus import Function, FunctionType
schema.add_field(field_name="text", datatype=DataType.VARCHAR,
max_length=1000, enable_analyzer=True)
schema.add_field(field_name="sparse", datatype=DataType.SPARSE_FLOAT_VECTOR)
bm25_function = Function(
name="text_bm25_emb",
input_field_names=["text"],
output_field_names=["sparse"],
function_type=FunctionType.BM25,
)
schema.add_function(bm25_function)
五、🔑 核心:五大索引类型怎么选?
索引决定了向量搜索的性能和精度,这是向量数据库的灵魂。 先看一张全景对比图:
| 索引类型 | 全称 | 适用场景 | 关键参数 | 内存占用 | 搜索速度 | 精度 |
|---|---|---|---|---|---|---|
| FLAT | 暴力扫描 | 小数据集、要求100%精度 | 无 | 高 | 慢 | 最高 |
| IVF_FLAT | 倒排文件 | 中等数据集 | nlist, nprobe |
中 | 中 | 高 |
| IVF_PQ | 倒排+乘积量化 | 大数据集、内存受限 | nlist, m, nbits |
低 | 快 | 中 |
| HNSW | 层次导航小世界 | 中大数据集、要求低延迟 | M, efConstruction, ef |
高 | 最快 | 高 |
| DiskANN | 磁盘近似最近邻 | 超大数据集 | search_list |
低 | 中 | 中高 |
各索引参数详解
1. FLAT ------ 暴力美学
适合小数据集,直接遍历所有向量,精度 100%。
2. IVF_FLAT ------ 先聚类再搜索
python
index_params.add_index(field_name="vector", metric_type="L2", index_type="IVF_FLAT",
params={"nlist": 64}) # 聚类中心数量,通常 4*sqrt(n)
# 搜索参数
search_params={"params": {"nprobe": 10}} # 搜索时检查的聚类数量
3. IVF_PQ ------ 乘积量化压缩(省内存利器)
python
index_params.add_index(field_name="vector", metric_type="L2", index_type="IVF_PQ",
params={"nlist": 64, "m": 32, "nbits": 8})
# m:子向量数量,dim/m >= 2(如 128/32=4)
# nbits:每个子向量编码位数,通常为 8
📐 压缩效果有多猛? 128 维 × 4 字节 = 512 字节 → 8 × 1 字节 = 8 字节,压缩 64 倍!
4. HNSW ------ 当前最流行(推荐首选)
python
index_params.add_index(field_name="vector", metric_type="L2", index_type="HNSW",
params={"M": 64, "efConstruction": 100})
# M:每个节点的最大邻居数(16~64),越大精度越高、内存越大
# efConstruction:构建时候选邻居数,越大构建越慢但质量越高
# 搜索参数
search_params={"params": {"ef": 10}} # 搜索候选邻居数,越大越精确
🔥 为什么 HNSW 最火? 召回率通常 >99%,接近精确搜索;查询延迟亚毫秒级(图遍历只需几跳);无需训练,不像 IVF 需要 K-Means 聚类。
5. DiskANN ------ 十亿级数据的救星
python
index_params.add_index(field_name="vector", metric_type="L2", index_type="DISKANN")
# 搜索参数
search_params={"params": {"search_list": 32}} # 候选列表大小
内存中只存 PQ 压缩向量(每条几十字节),原始向量存 SSD。内存需求仅需数据量的 1/10 ~ 1/20,单机可处理十亿级!
度量方式怎么选?
| 度量类型 | 适用场景 | 距离含义 |
|---|---|---|
| L2 | 连续数据、绝对距离 | 欧几里得距离(越小越相似) |
| IP(内积) | 已归一化的向量 | 内积值(越大越相似) |
| COSINE | 方向相似性、语义搜索 | 余弦相似度(越大越相似) |
💡 选型口诀 :语义搜索首选
COSINE;向量已归一化用IP(更快);需要精确距离用L2。注:向量 L2 归一化后,COSINE 和 IP 等价,但用 IP 省去除法计算更快。
六、搜索全家桶:从 ANN 到迭代搜索
Milvus 的搜索能力非常丰富,一个 search() 方法能玩出花:
| 搜索类型 | 核心方法 | 用途 |
|---|---|---|
| ANN 近似搜索 | client.search() |
基本向量相似搜索 |
| 过滤搜索 | client.search(filter=...) |
结合标量过滤条件 |
| 范围搜索 | + radius/range_filter |
查找距离在指定范围内的向量 |
| 分组搜索 | + group_by_field |
按字段分组去重 |
| 全文搜索 | + BM25 |
关键词匹配 |
| 文本匹配 | TEXT_MATCH() |
精确文本匹配过滤 |
| 迭代搜索 | client.search_iterator() |
大规模分批获取结果 |
| 混合搜索 | collection.hybrid_search() |
多路检索融合 |
1. ANN 基本搜索
python
# 单向量搜索
results = client.search(
collection_name="my_col",
data=[query_vector], # 查询向量
anns_field="vector", # 向量字段名
limit=3, # 返回 top-K
search_params={"metric_type": "L2"},
output_fields=["color"] # 返回的标量字段
)
2. 过滤搜索:标准 vs 迭代
python
# 标准过滤:先 ANN 搜索,再过滤
results = client.search(
...,
filter='color like "color_%" and likes > 500',
output_fields=["color", "likes"]
)
# 迭代过滤(hints="iterative_filter"):边搜索边过滤,精度更高
results = client.search(
...,
search_params={"metric_type": "L2", "hints": "iterative_filter"},
filter='color like "color_%" and likes > 500'
)
⚠️ 面试考点:当过滤条件会筛掉大量结果时,标准过滤可能返回不足,迭代过滤更可靠。
3. 范围搜索
python
# L2 距离:range_filter < radius(距离 0.5~1.0 之间)
results = client.search(
...,
search_params={
"metric_type": "L2",
"params": {"radius": 1.0, "range_filter": 0.5}
}
)
⚠️ 方向别搞反 :L2 距离
range_filter < radius;IP/COSINE 则range_filter > radius。
4. 分组搜索 ------ RAG 必备!
python
results = client.search(
...,
group_by_field="docId", # 按文档ID分组
group_size=2, # 每组返回2个结果
strict_group_size=True # 严格确保每组有足够结果
)
💡 为什么 RAG 场景必须有它? 同一文档被切成多个 chunk,不分组时同一文档的多个 chunk 可能霸占整个结果列表,导致返回内容单一。
5. 全文搜索(BM25)
python
# 配置 BM25 索引
index_params.add_index(
field_name="sparse",
index_type="SPARSE_INVERTED_INDEX",
metric_type="BM25",
params={"inverted_index_algo": "DAAT_MAXSCORE", "bm25_k1": 1.2, "bm25_b": 0.75}
)
# 直接用文本搜索(无需手动编码向量!)
results = client.search(
collection_name="my_col",
data=["信息检索"], # 直接传入文本
anns_field='sparse',
limit=3,
search_params={'params': {'drop_ratio_search': 0.2}},
output_fields=["text"]
)
6. 迭代搜索(SearchIterator)
适合需要获取大量结果的场景,分批返回:
python
iterator = client.search_iterator(
collection_name="my_col",
data=[query_vector],
anns_field="vector",
search_params={"metric_type": "L2"},
batch_size=1000, # 每批1000条
limit=20000, # 总共最多20000条
output_fields=["color"]
)
while True:
result = iterator.next()
if not result:
iterator.close()
break
for hit in result:
all_results.append(hit.to_dict())
七、LlamaIndex:更高层的抽象
直接用 Milvus 写代码比较繁琐,LlamaIndex 提供了更高层的封装:
| 概念 | 说明 |
|---|---|
| Document | 原始文档(来自文件、网页等) |
| Node | 文档的一个片段(chunk),是索引和检索的基本单元 |
| Index | 数据结构,将 Node 组织为可检索的形式 |
| VectorStoreIndex | 最常用的索引类型,基于向量存储 |
四步构建索引
python
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.node_parser import SentenceSplitter
# 1. 加载文档
documents = SimpleDirectoryReader("90-文档-Data").load_data()
# 2. 自定义切分(可选)
text_splitter = SentenceSplitter(chunk_size=512, chunk_overlap=10)
nodes = text_splitter.get_nodes_from_documents(documents)
# 3. 构建索引(从文档或从节点)
index = VectorStoreIndex.from_documents(documents) # 自动切分
# 或
index = VectorStoreIndex(nodes) # 使用自定义切分
# 4. 持久化到磁盘
index.storage_context.persist(persist_dir="saved_index")
LlamaIndex 帮你自动管理这些繁琐工作:节点切分、向量嵌入、文档关系、元数据保存,开发者只需关注业务逻辑。
八、🔥 重头戏:混合检索(Dense + Sparse)
混合检索是当前 RAG 系统的标配方案,结合了密集向量和稀疏向量的优势。
为什么必须混合?
用一个经典例子说明:
查询: "苹果公司的市值"
Dense(语义匹配)✅ Sparse(关键词匹配)✅
┌─────────────────────┐ ┌─────────────────────┐
│ "Apple 的股价走势" │ ←语义→ │ │
│ "库克领导下的科技巨头" │ ←语义→ │ │
│ "香蕉的价格" │ ✗不匹配 │ "苹果公司发布新品" │ ← 精确匹配"苹果公司"
└─────────────────────┘ │ "市值的计算方法" │ ← 精确匹配"市值"
└─────────────────────┘
- Dense 单独用:可能召回"苹果(水果)的价格"(语义混淆)
- Sparse 单独用:可能漏掉"Apple 股价"(用词不同)
- 融合后 :Dense 解决"词不同但意思相同",Sparse 解决"词相同但意思不同",完美互补!
核心模型:BGE-M3
BAAI/bge-m3 一次编码同时输出密集 + 稀疏向量:
python
from milvus_model.hybrid import BGEM3EmbeddingFunction
ef = BGEM3EmbeddingFunction(use_fp16=False, device="cpu")
docs_embeddings = ef(docs)
# 密集向量:docs_embeddings["dense"] → shape: (n, 1024)
# 稀疏向量:docs_embeddings["sparse"] → scipy 稀疏矩阵
架构示意
查询文本 → BGE-M3 编码 → 密集向量 ──→ ANN 搜索 (dense_vector) ──→ ┐
稀疏向量 ──→ 稀疏搜索 (sparse_vector) ──→ ├── 重排序 → 最终结果
双索引配置
python
# 稀疏向量索引
collection.create_index("sparse_vector", {"index_type": "SPARSE_INVERTED_INDEX", "metric_type": "IP"})
# 密集向量索引
collection.create_index("dense_vector", {"index_type": "AUTOINDEX", "metric_type": "IP"})
两种重排序策略
1. WeightedRanker(加权重排)
python
from pymilvus import AnnSearchRequest, WeightedRanker
dense_req = AnnSearchRequest(data=[dense_vec], anns_field="dense_vector", param=params, limit=5)
sparse_req = AnnSearchRequest(data=[sparse_vec], anns_field="sparse_vector", param=params, limit=5)
rerank = WeightedRanker(0.5, 1.0) # 稀疏权重0.5,密集权重1.0
results = collection.hybrid_search(reqs=[dense_req, sparse_req], rerank=rerank, limit=5)
2. RRFRanker(基于排名的倒数融合)
python
from pymilvus import RRFRanker
rerank = RRFRanker(k=60) # k 为平滑参数
results = collection.hybrid_search(reqs=[dense_req, sparse_req], rerank=rerank, limit=5)
💡 选型建议 :需要精确控制权重用
WeightedRanker;不确定权重用RRFRanker(更鲁棒)。
九、BGE-M3 与 ColBERT:延迟交互到底牛在哪?
BGE-M3 的 "M3" 指 Multi-lingual(多语言)、Multi-granularity(多粒度)、Multi-function(多功能) 。其中 Multi-function 是最大亮点:一个模型同时输出三种表示!
| 表示 | 维度/格式 | 捕获的信息 | 等价于 |
|---|---|---|---|
| Dense(密集向量) | 1024 维 float | 语义相似性 | 类似 BGE-large 的嵌入 |
| Sparse(稀疏向量) | 词 → 权重的稀疏映射 | 关键词匹配 | 类似 BM25 / SPLADE |
| ColBERT(多向量) | token × 1024 矩阵 | 细粒度 token 级交互 | 类似 ColBERT late interaction |
三种交互方式对比
┌─────────────────────────────────────────────────────────┐
│ Dense(单向量交互) │
│ Query: [v_q] ──── 余弦相似度 ──── Doc: [v_d] │
│ 1 个向量 vs 1 个向量 → 1 次计算 │
│ ✅ 速度快 ❌ 丢失细粒度信息 │
├─────────────────────────────────────────────────────────┤
│ Sparse(稀疏交互) │
│ Query: {"苹果":0.8, "市值":0.6} │
│ Doc: {"苹果":0.5, "公司":0.3} → 词级别匹配 │
│ ✅ 精确匹配 ❌ 无法理解语义 │
├─────────────────────────────────────────────────────────┤
│ ColBERT(延迟交互 / Late Interaction) │
│ Query tokens: [q0] [q1] [q2] [q3] │
│ ╳ ╳ ╳ ╳ ← 每对 token 都算 │
│ Doc tokens: [d0] [d1] [d2] [d3] [d4] [d5] │
│ N × M 次计算 → 精细的 token 级交互 │
│ ✅ 最精细的匹配 ❌ 计算和存储开销大 │
└─────────────────────────────────────────────────────────┘
ColBERT 打分公式
Score(Q, D) = Σ_i max_j sim(q_i, d_j)
即:对 Query 中每个 token,找到 Document 中与它最相似的 token,
把所有最佳匹配分数加起来作为最终得分。
🔑 一句话理解:Dense 是"早期交互"(先把文档压缩成一个向量,信息已丢失);ColBERT 是"延迟交互"(保留每个 token 的向量,打分时才交互),所以精度最高。
ColBERT 适合什么场景?
✅ 最适合:
- 实体密集型检索:精确匹配人名、地名、产品名(如"iPhone 15 Pro Max 的价格")
- 长文档段落检索:在 10 页合同中精确定位"违约赔偿条款"
- 细微差异区分:"利率 3.5%" vs "利率 4.5%"
- 跨语言 token 对齐:"Elon Musk" ↔ "埃隆·马斯克"
❌ 不适合:
- 超大规模数据(>千万):每个文档存 N 个向量,开销太大
- 实时性要求极高:延迟交互需 N×M 次计算
- 短文本相似度:文本太短,token 级交互优势不明显
Milvus 能直接存 ColBERT 吗?
目前不能直接存 。原因:Milvus 的 FLOAT_VECTOR 字段要求固定维度,而 ColBERT 每个 doc 的 token 数不同(变长)。
最佳实践 ⭐:
用 Dense + Sparse 做初筛召回(百万 → 百级)
再用 ColBERT / Cross-Encoder 做 Rerank 精排(百 → Top-10)
这样兼顾了规模和精度,是工业界最常用的方案。
十、多模态检索:图文跨模态怎么玩
基于 Visualized-BGE 模型,实现图像 + 文本的跨模态检索。
核心能力
在 BGE 基础上增加视觉编码器,支持两种模式:
python
from visual_bge.modeling import Visualized_BGE
encoder = Visualized_BGE(model_name_bge="BAAI/bge-m3", model_weight="./Visualized_m3.pth")
# 纯图像编码
image_vector = encoder.encode(image="image.jpg")
# 图文联合编码(用于查询)
query_vector = encoder.encode(image="query.jpg", text="寻找战斗场景")
检索流程
1. 数据准备:加载图像 + metadata.json
2. 编码入库:每张图像 → Visual-BGE 编码 → Milvus 存储
3. 查询检索:查询图像 + 查询文本 → 联合编码 → 向量搜索 → 返回相似图像
4. 可视化:查询图 + 检索结果 → 网格拼图展示
带过滤条件的搜索
python
# 带余弦相似度范围的搜索
search_params = {
"metric_type": "COSINE",
"params": {"nprobe": 10, "radius": 0.1, "range_filter": 0.8}
}
# 带元数据过滤的搜索
filters = {"environment": "雪地", "category": "combat"}
# 等价于 filter 表达式: environment == '雪地' and category == 'combat'
中英文版本
| 版本 | 模型 | 语言 |
|---|---|---|
| 中文版 | BAAI/bge-m3 + Visualized_m3.pth |
中文查询 |
| 英文版 | BAAI/bge-base-en-v1.5 + Visualized_base_en_v1.5.pth |
英文查询 |
十一、🎯 速查表合集
索引选型决策树
数据量级?
├── < 10万 → FLAT(精度优先)
├── 10万~100万 → HNSW(速度+精度平衡)
├── 100万~1000万 → IVF_FLAT / IVF_PQ(内存可控)
└── > 1000万 → DiskANN(磁盘存储)
搜索模式选择
需求?
├── 语义相似 → 密集向量搜索(COSINE/IP)
├── 关键词匹配 → 稀疏向量搜索(BM25/Sparse)
├── 语义+关键词 → 混合搜索(hybrid_search)
├── 图像+文本 → 多模态检索(Visual-BGE)
├── 条件过滤 → 过滤搜索(filter 参数)
├── 按文档去重 → 分组搜索(group_by_field)
└── 大量结果 → 迭代搜索(search_iterator)
嵌入模型对照表
| 模型 | 类型 | 维度 | 特点 |
|---|---|---|---|
BAAI/bge-m3 |
多语言密集+稀疏 | 1024 | 混合检索首选 |
BAAI/bge-large-zh |
中文密集 | 1024 | 中文语义搜索 |
BAAI/bge-base-en-v1.5 |
英文密集 | 768 | 英文语义搜索 |
| Visualized-BGE | 多模态 | 视模型而定 | 图文跨模态检索 |
text-embedding-3-large |
OpenAI API | 3072 | 云端 API |
Milvus 操作六步法
python
from pymilvus import MilvusClient, DataType
client = MilvusClient(uri="http://localhost:19530")
# 创建 → 插入 → 索引 → 加载 → 搜索 → 释放
client.create_collection(collection_name="demo", dimension=768)
client.insert(collection_name="demo", data=data)
client.create_index(collection_name="demo", index_params=index_params)
client.load_collection(collection_name="demo")
results = client.search(collection_name="demo", data=[query_vec], limit=5)
client.release_collection(collection_name="demo")
十二、20 道高频面试题(建议背诵)
Q1:什么是向量数据库?和传统关系型数据库的区别?
核心区别在于查询方式 :关系型数据库做精确匹配(WHERE、JOIN),向量数据库做相似度搜索(ANN)。向量数据库解决高维空间中的相似度检索问题,这是传统数据库无法高效完成的。
Q2:什么是 ANN?为什么不总用精确搜索?
- KNN(精确):遍历所有向量,
O(n) - ANN(近似):索引结构,亚线性时间
不用精确搜索的原因:数据量大时延迟不可接受;向量本身是近似表示,100% 精确意义不大;95%+ 召回率通常已够用。
Q3:L2、IP、COSINE 的区别?
- L2:欧几里得距离,需绝对距离场景,受模长影响
- IP:内积,向量已归一化时用,未归一化会失真
- COSINE:余弦相似度,只看方向,语义搜索首选
向量 L2 归一化后 COSINE 和 IP 等价,用 IP 更快(省去除法)。
Q4:Milvus 五种索引的原理和适用场景?
| 索引 | 原理 | 适用规模 | 召回率 | 速度 | 内存 |
|---|---|---|---|---|---|
| FLAT | 暴力扫描 | <10万 | 100% | 最慢 | 高 |
| IVF_FLAT | K-Means 分桶,查 nprobe 个桶 | 百万级 | ~95% | 快 | 中 |
| IVF_PQ | 分桶 + 乘积量化压缩 | 亿级 | ~90% | 快 | 低 |
| HNSW | 多层导航图 | 千万级 | >99% | 最快 | 高 |
| DiskANN | 内存存 PQ 压缩向量,SSD 存原始 | 十亿级 | ~95% | 较快 | 低 |
Q5:HNSW 原理?为什么最流行?
分层可导航小世界图:上层稀疏(长距离边快速跨越),下层稠密(短距离边精确搜索)。从顶层入口贪心搜索,逐层下降到 Layer 0。
流行的原因:召回率 >99%、查询延迟亚毫秒级、无需训练。核心参数:M(16~64)、efConstruction、ef。
Q6:IVF_PQ 乘积量化怎么压缩?
- D 维向量切分为 M 个子空间
- 每个子空间独立 K-Means(256 中心),形成码本
- 每段用 1 字节(0~255)代替 float32
压缩效果:128 维 512 字节 → 8 字节,压缩 64 倍。
Q7:DiskANN 如何处理十亿级向量?
使用 Vamana 图算法构建全局导航图;内存只存 PQ 压缩向量(几十字节),原始向量存 SSD;查询时内存筛选候选 → SSD 读精确向量 → 重排;通过数据布局优化将随机读变为近似顺序读。
内存需求仅需数据量 1/10~1/20,单机处理十亿级。
Q8:标准过滤和迭代过滤的区别?
- 标准过滤:先 ANN 得 Top-K → 再 filter 过滤,可能返回不足
- 迭代过滤 (
hints="iterative_filter"):边搜索边过滤,扩大搜索范围,精度更高
过滤条件会筛掉大量结果时,迭代过滤更可靠。
Q9:什么是分组搜索?
按 group_by_field 分组,每组只返回最相似的 top-K。解决 RAG 中同一文档多个 chunk 占满结果的问题。
Q10:collection 的 load 和 release?
- load:磁盘 → 内存(搜索前必须 load)
- release:内存释放数据
Milvus 搜索在内存中执行,这是内存-磁盘分离架构的体现。
Q11:为什么 Dense + Sparse 要结合?
- Dense 擅长语义理解,但可能语义混淆("苹果"水果 vs 公司)
- Sparse 擅长精确匹配,但无法理解用词不同的同义表达
Dense 解决"词不同意思同",Sparse 解决"词同意不同",互补。
Q12:BGE-M3 能同时做语义和关键词搜索吗?
能。一次编码同时输出三种表示:Dense(1024 维,语义)、Sparse(关键词)、ColBERT(token 级)。一个模型顶过去 BGE + BM25 两个。
Q13:WeightedRanker vs RRFRanker?
- WeightedRanker:分数加权求和,知道哪路更可靠时用
- RRFRanker :
1/(k+rank)排名倒数融合,不确定权重时用,更鲁棒
Q14:ColBERT 的延迟交互?
Dense 是早期交互(压缩时丢信息),ColBERT 保留每个 token 向量,打分时才交互:Score(Q,D) = Σ_i max_j sim(q_i, d_j)。精度最高,但存储计算开销大。
Q15:Milvus 能直接存 ColBERT 多向量吗?
不能 。FLOAT_VECTOR 要求固定维度,ColBERT 每个 doc token 数不同(变长)。替代方案:RAGatouille 等专用库,或 Dense+Sparse 初筛 + ColBERT 精排。
Q16:Visualized-BGE 如何跨模态检索?
在 BGE 基础上增加视觉编码器:纯图像编码 + 图文联合编码。入库时单图像编码,查询时图文联合编码后搜索相似向量。
Q17:法律文档检索系统怎么设计?
- 嵌入模型:BGE-M3
- 数据库:Milvus(HNSW + 混合搜索)
- 策略:Dense+Sparse 初筛 → ColBERT/Cross-Encoder 精排 → 分组去重
- Schema:双向量 + docId 分组 + 法律领域标量字段
- 过滤:案由、法院、时间范围
理由:法律对精确性要求极高,需要 ColBERT 的 token 级匹配。
Q18:10 万 → 10 亿索引如何演进?
| 阶段 | 数据量 | 索引 | 架构 |
|---|---|---|---|
| 初期 | <10万 | FLAT | 单机 Lite |
| 成长期 | 10万~100万 | HNSW | Standalone |
| 扩展期 | 100万~1000万 | IVF_FLAT/PQ | 调优参数 |
| 大规模 | 1000万~1亿 | IVF_PQ | Cluster + 分区 |
| 超大规模 | >1亿 | DiskANN | SSD + 内存压缩 |
Q19:Partition 和 Alias 解决什么问题?
- Partition:集合内部物理隔离,加速查询(按时间/地区分区)
- Alias:零停机切换集合(重建索引时新建 v2 → 切换别名)
Q20:RAG 召回率不够怎么优化?
按优先级:
- 换用混合检索(Dense + Sparse),通常 +10%
- 换索引(IVF_PQ → HNSW,~90% → >99%)
- 调参(增大
nprobe/ef) - 加 Rerank(召回 Top-100,Cross-Encoder 精排 Top-10)
- 优化 Embedding 模型
- 优化分块策略
- 查询扩展(LLM 改写查询)
十三、总结
这篇文章把向量数据库的核心知识系统地梳理了一遍,关键要点回顾:
🎯 三步上手 Milvus:
- 理解 Collection / Partition / Alias 三层抽象
- 掌握 Schema 设计(密集向量 + 稀疏向量 + 标量字段)
- 索引选型牢记决策树:小数据 FLAT/HNSW,大数据 PQ/DiskANN
🔥 RAG 黄金组合:
BGE-M3 编码 → Dense + Sparse 混合检索初筛 → ColBERT/Cross-Encoder Rerank 精排
💡 三个关键认知:
- ANN 牺牲少量精度换数量级速度,95% 召回率通常够用
- Dense + Sparse 互补,一个管语义一个管关键词
- ColBERT 精度最高但开销大,适合做 Rerank 而非初筛
📌 写在最后 :向量数据库是 AI 时代的基础设施,无论是 RAG、推荐系统还是多模态搜索都离不开它。这篇文章可以作为手册随时翻阅,建议收藏!
如果觉得有帮助,欢迎点赞 + 关注 + 收藏一键三连 ❤️,你的支持是我持续输出的动力!
有任何问题欢迎在评论区交流,我会一一回复~ 🙌
🏷️ 标签 :向量数据库 Milvus RAG 混合检索 BGE-M3 HNSW 语义搜索 人工智能