本教程基于项目实际代码(
pymilvus+MilvusClient),面向有 Python 基础的开发者,从概念到实战快速上手。
目录
- [Milvus 是什么](#Milvus 是什么)
- 核心概念
- 环境准备
- [连接 Milvus](#连接 Milvus)
- 创建集合(Collection)
- 插入数据
- 向量搜索
- [混合搜索(稠密 + 稀疏向量)](#混合搜索(稠密 + 稀疏向量))
- 标量过滤查询
- 删除与清理
- 工程实践:客户端管理
- 常见问题
1. Milvus 是什么
Milvus 是一款开源的向量数据库,专为 AI 应用设计。它解决了传统数据库无法高效处理"语义相似度搜索"的问题。
典型应用场景:
| 场景 | 说明 |
|---|---|
| RAG(检索增强生成) | 将文档切片 → 向量化 → 存入 Milvus → 用户提问时检索最相关的片段 → 交给 LLM 生成回答 |
| 以图搜图 | 将图片特征向量存入 Milvus,用查询图片的向量检索相似图片 |
| 推荐系统 | 将用户/商品向量化,检索最相似的用户或商品 |
为什么不用传统数据库? 传统数据库用精确匹配(=、LIKE),而向量搜索是找"最相似的"------就像用"意思相近"来搜索,而不是"字面匹配"。
2. 核心概念
┌─────────────────────────────────────────────────┐
│ Collection(集合) │
│ ┌─────────────────────────────────────────────┐ │
│ │ Schema(模式/表结构) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ chunk_id │ │ dense │ │ content │ │ │
│ │ │ (主键) │ │ _vector │ │ (VARCHAR) │ │ │
│ │ │ INT64 │ │FLOAT_VEC │ │ │ │ │
│ │ └──────────┘ └──────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Index(索引) │ │
│ │ 为向量字段建立索引,加速相似度搜索 │ │
│ │ 类型:AUTOINDEX / HNSW / IVF_FLAT 等 │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
| 概念 | 类比 | 说明 |
|---|---|---|
| Collection | 数据库中的"表" | 存储数据的基本单位 |
| Schema | 表的"列定义" | 定义每个字段的名称、类型、约束 |
| Field | 表中的"列" | 分为标量字段(文本、数字)和向量字段 |
| Index | 数据库的"索引" | 加速向量搜索,必须在搜索前创建 |
| Partition | 表的"分区" | 可选,用于数据隔离和加速查询 |
| Entity | 表中的"行" | 一条完整的数据记录 |
3. 环境准备
3.1 安装 Milvus 服务端
方式一:Docker 部署(推荐开发用)
bash
# 下载并启动 Milvus Standalone
wget https://github.com/milvus-io/milvus/releases/download/v2.5.4/milvus-standalone-docker-compose.yml -O docker-compose.yml
docker compose up -d
方式二:Milvus Lite(零部署,适合学习)
bash
pip install -U pymilvus
Milvus Lite 是 pymilvus 内置的轻量版,数据保存在本地文件,无需启动服务端:
python
from pymilvus import MilvusClient
# 数据保存在本地文件中
client = MilvusClient("my_milvus.db")
3.2 安装 Python SDK
bash
pip install -U pymilvus
3.3 环境变量配置
在 .env 文件中配置连接信息:
env
# Milvus 连接地址
MILVUS_URL=http://192.168.6.160:19530
# 知识库集合名
CHUNKS_COLLECTION=kb_chunks_v1
4. 连接 Milvus
4.1 基础连接
python
from pymilvus import MilvusClient
# 连接到远程 Milvus 服务
client = MilvusClient(uri="http://192.168.6.160:19530")
# 连接 Milvus Lite(本地文件)
client = MilvusClient("my_data.db")
# 连接带认证的 Milvus
client = MilvusClient(
uri="http://localhost:19530",
token="root:Milvus"
)
4.2 工程化:线程安全的客户端管理
在生产环境中,推荐使用双重检查锁 + 延迟初始化模式管理客户端,避免重复创建和并发问题:
python
import os
import threading
from typing import Optional
from pymilvus import MilvusClient
class StorageClients:
"""存储客户端管理器"""
_milvus_client: Optional[MilvusClient] = None
_milvus_lock = threading.Lock()
@classmethod
def get_milvus_client(cls) -> MilvusClient:
"""获取 Milvus 客户端(线程安全单例)"""
# 第一次检查(无锁,快速返回)
if cls._milvus_client is not None:
return cls._milvus_client
with cls._milvus_lock:
# 第二次检查(有锁,防止并发重复创建)
if cls._milvus_client is not None:
return cls._milvus_client
milvus_uri = os.getenv("MILVUS_URL")
if not milvus_uri:
raise EnvironmentError("缺少环境变量 MILVUS_URL")
cls._milvus_client = MilvusClient(milvus_uri)
return cls._milvus_client
为什么要延迟初始化? 把"创建"动作延迟到真正需要的那一刻,避免程序启动时因 Milvus 未就绪而报错。
5. 创建集合(Collection)
创建集合需要三步:定义 Schema → 创建索引 → 创建集合。
5.1 快速创建(简单场景)
MilvusClient 提供了一行代码创建集合的快捷方式:
python
client.create_collection(
collection_name="my_collection",
dimension=1024, # 向量维度,必须与 Embedding 模型一致
metric_type="COSINE", # 余弦相似度
)
5.2 自定义 Schema(生产推荐)
当需要多个标量字段或混合向量时,需要手动构建 Schema:
python
from pymilvus import DataType, MilvusClient
client = MilvusClient(uri="http://192.168.6.160:19530")
# ── 第 1 步:构建 Schema ──
schema = client.create_schema(enable_dynamic_field=True)
# 主键字段(自增 ID)
schema.add_field(
field_name="chunk_id",
datatype=DataType.INT64,
is_primary=True,
auto_id=True
)
# 稠密向量字段
schema.add_field(
field_name="dense_vector",
datatype=DataType.FLOAT_VECTOR,
dim=1024 # 维度必须与 Embedding 模型输出一致
)
# 稀疏向量字段(用于混合搜索)
schema.add_field(
field_name="sparse_vector",
datatype=DataType.SPARSE_FLOAT_VECTOR
)
# 标量字段
schema.add_field(field_name="content", datatype=DataType.VARCHAR, max_length=65535)
schema.add_field(field_name="title", datatype=DataType.VARCHAR, max_length=65535)
schema.add_field(field_name="item_name", datatype=DataType.VARCHAR, max_length=65535)
# ── 第 2 步:创建索引 ──
index_params = client.prepare_index_params()
# 稠密向量索引
index_params.add_index(
field_name="dense_vector",
index_name="dense_vector_index",
index_type="AUTOINDEX", # 自动选择最优索引类型
metric_type="COSINE" # 余弦相似度
)
# 稀疏向量索引
index_params.add_index(
field_name="sparse_vector",
index_name="sparse_vector_index",
index_type="SPARSE_INVERTED_INDEX", # 稀疏向量专用索引
metric_type="IP" # 内积
)
# ── 第 3 步:创建集合 ──
client.create_collection(
collection_name="kb_chunks_v1",
schema=schema,
index_params=index_params
)
5.3 常用向量索引类型
| 索引类型 | 适用场景 | 说明 |
|---|---|---|
AUTOINDEX |
通用场景 | Milvus 自动选择最优索引,推荐新手使用 |
HNSW |
高召回、高 QPS | 基于图的索引,搜索速度快,内存占用较高 |
IVF_FLAT |
大规模数据 | 倒排索引 + 精确距离计算,平衡精度和速度 |
SPARSE_INVERTED_INDEX |
稀疏向量 | 稀疏向量专用索引 |
5.4 常用相似度度量
| 度量方式 | 说明 | 适用场景 |
|---|---|---|
COSINE |
余弦相似度 | 文本语义搜索(最常用) |
L2 |
欧氏距离 | 图像特征匹配 |
IP |
内积 | 稀疏向量、归一化后的向量 |
5.5 检查集合是否存在
python
if client.has_collection("kb_chunks_v1"):
print("集合已存在")
else:
print("集合不存在")
6. 插入数据
6.1 插入单条/多条数据
python
data = [
{
"dense_vector": [0.1] * 1024,
"sparse_vector": {1: 0.5, 100: 0.3, 500: 0.8},
"content": "Milvus 是一款高性能向量数据库",
"title": "Milvus 简介",
"item_name": "Milvus"
},
{
"dense_vector": [0.2] * 1024,
"sparse_vector": {2: 0.4, 200: 0.6},
"content": "RAG 技术通过外挂知识库解决大模型幻觉问题",
"title": "RAG 技术",
"item_name": "RAG"
}
]
result = client.insert(
collection_name="kb_chunks_v1",
data=data
)
print(f"插入数量: {result['insert_count']}")
print(f"自增 ID: {result['ids']}")
6.2 批量插入(大数据量)
python
# 分批插入,每批 100 条
batch_size = 100
for i in range(0, len(all_data), batch_size):
batch = all_data[i : i + batch_size]
client.insert(collection_name="kb_chunks_v1", data=batch)
注意 :
sparse_vector字段的值是一个字典{token_id: weight},例如{1: 0.5, 100: 0.3},表示第 1 维权重 0.5、第 100 维权重 0.3。
7. 向量搜索
7.1 基础向量搜索
python
# 查询向量(通常由 Embedding 模型生成)
query_vector = [0.12] * 1024
results = client.search(
collection_name="kb_chunks_v1",
data=[query_vector],
limit=5, # 返回最相似的 5 条
output_fields=["content", "title"], # 需要返回的标量字段
search_params={"metric_type": "COSINE"}
)
# 结果是一个列表,每个元素对应一个查询向量的结果
for hit in results[0]:
print(f"ID: {hit['id']}, 距离: {hit['distance']}, 内容: {hit['entity']['content']}")
7.2 带过滤条件的搜索
python
results = client.search(
collection_name="kb_chunks_v1",
data=[query_vector],
limit=5,
filter='item_name == "Milvus"', # 标量过滤
output_fields=["content", "title", "item_name"],
search_params={"metric_type": "COSINE"}
)
8. 混合搜索(稠密 + 稀疏向量)
混合搜索同时使用稠密向量和稀疏向量,通过重排序器融合两路结果,兼顾语义理解和关键词精确匹配,效果优于单一向量搜索。
8.1 流程概览
用户查询
│
├──→ BGE-M3 编码 ──→ 稠密向量 ──→ AnnSearchRequest ──┐
│ ├──→ WeightedRanker ──→ 最终结果
└──→ BGE-M3 编码 ──→ 稀疏向量 ──→ AnnSearchRequest ──┘
8.2 创建混合搜索请求
python
from pymilvus import AnnSearchRequest, WeightedRanker
def create_hybrid_search_requests(
dense_vector, sparse_vector,
expr=None, expr_params=None, limit=5
):
"""创建稠密 + 稀疏两路搜索请求"""
# 稠密向量搜索请求
dense_req = AnnSearchRequest(
data=[dense_vector],
anns_field="dense_vector",
param={"metric_type": "COSINE"},
expr=expr,
expr_params=expr_params,
limit=limit
)
# 稀疏向量搜索请求
sparse_req = AnnSearchRequest(
data=[sparse_vector],
anns_field="sparse_vector",
param={"metric_type": "IP"},
expr=expr,
expr_params=expr_params,
limit=limit
)
return [dense_req, sparse_req]
8.3 执行混合搜索
python
def execute_hybrid_search(
client, collection_name, search_requests,
ranker_weights=(0.5, 0.5), limit=5, output_fields=None
):
"""执行混合搜索并融合结果"""
# 创建权重融合排序器
reranker = WeightedRanker(
ranker_weights[0], # 稠密向量权重
ranker_weights[1], # 稀疏向量权重
norm_score=True # 归一化分数
)
if output_fields is None:
output_fields = ["content", "item_name"]
results = client.hybrid_search(
collection_name=collection_name,
reqs=search_requests,
ranker=reranker,
limit=limit,
output_fields=output_fields
)
return results
8.4 完整调用示例
单个向量查询
python
from pymilvus.model.hybrid import BGEM3EmbeddingFunction
from pymilvus import MilvusClient, AnnSearchRequest, WeightedRanker
# 1. 加载 BGE-M3 模型
model = BGEM3EmbeddingFunction("BAAI/bge-m3", device="cpu", use_fp16=False)
# 2. 编码查询文本
query_result = model.encode_queries(["什么是向量数据库?"])
dense_vec = query_result["dense"][0].tolist()
csr_array = query_result['sparse']
# 处理稀疏向量
sparse_vec = []
for i in range(csr_array.shape[0]):
start = csr_array.indptr[i]
end = csr_array.indptr[i + 1]
token_ids = csr_array.indices[start:end].tolist()
weights = csr_array.data[start:end].tolist()
sparse_vec.append(dict(zip(token_ids, weights)))
sparse_vec = sparse_vec[0] # 取第一个查询的稀疏向量
# 3. 创建搜索请求(修正为正确的 API)
dense_search_req = AnnSearchRequest(
data=[dense_vec],
anns_field="dense_vector", # 替换为实际的稠密向量字段名
param={"metric_type": "COSINE", "params": {"nprobe": 10}},
limit=5
)
sparse_search_req = AnnSearchRequest(
data=[sparse_vec],
anns_field="sparse_vector", # 替换为实际的稀疏向量字段名
param={"metric_type": "IP"}, # 稀疏向量通常使用 IP
limit=5
)
# 4. 执行混合搜索
client = MilvusClient(uri="http://192.168.6.160:19530")
results = client.hybrid_search(
collection_name="kb_chunks_v1",
reqs=[dense_search_req, sparse_search_req],
ranker=WeightedRanker(0.7, 0.3), # 稠密权重 0.7,稀疏权重 0.3
limit=5,
output_fields=["content", "title"]
)
# 5. 输出结果
for hit in results[0]:
print(f"距离: {hit['distance']:.4f} | {hit['entity']}")
多个向量查询
python
from pymilvus.model.hybrid import BGEM3EmbeddingFunction
from pymilvus import MilvusClient, AnnSearchRequest, WeightedRanker
# 1. 加载 BGE-M3 模型
model = BGEM3EmbeddingFunction("BAAI/bge-m3", device="cpu", use_fp16=False)
# 2. 编码多个查询文本
query_texts = ["什么是向量数据库?", "如何实现混合搜索?", "BGE-M3 模型有什么特点?"]
query_result = model.encode_queries(query_texts)
# 提取所有查询的稠密向量
dense_vectors = [vec.tolist() for vec in query_result["dense"]]
# 提取所有查询的稀疏向量
csr_array = query_result['sparse']
sparse_vectors = []
for i in range(csr_array.shape[0]): # 遍历所有查询
start = csr_array.indptr[i]
end = csr_array.indptr[i + 1]
token_ids = csr_array.indices[start:end].tolist()
weights = csr_array.data[start:end].tolist()
sparse_vectors.append(dict(zip(token_ids, weights)))
# 3. 为每个查询创建独立的搜索请求
dense_search_reqs = []
sparse_search_reqs = []
for i in range(len(query_texts)):
# 稠密搜索请求
dense_req = AnnSearchRequest(
data=[dense_vectors[i]],
anns_field="dense_vector",
param={"metric_type": "COSINE", "params": {"nprobe": 10}},
limit=5
)
dense_search_reqs.append(dense_req)
# 稀疏搜索请求
sparse_req = AnnSearchRequest(
data=[sparse_vectors[i]],
anns_field="sparse_vector",
param={"metric_type": "IP"},
limit=5
)
sparse_search_reqs.append(sparse_req)
# 4. 执行混合搜索(对每个查询分别搜索)
client = MilvusClient(uri="http://192.168.6.160:19530")
all_results = []
for i in range(len(query_texts)):
print(f"\n查询 {i+1}: {query_texts[i]}")
print("-" * 50)
# 对当前查询执行混合搜索
results = client.hybrid_search(
collection_name="kb_chunks_v1",
reqs=[dense_search_reqs[i], sparse_search_reqs[i]],
ranker=WeightedRanker(0.7, 0.3),
limit=5,
output_fields=["content", "title"]
)
all_results.append(results)
# 5. 输出所有查询的结果
for query_idx, results in enumerate(all_results):
print(f"\n{'='*60}")
print(f"查询 {query_idx + 1}: {query_texts[query_idx]}")
print('='*60)
# 每个查询返回的结果
for hit_idx, hit in enumerate(results[0]):
print(f"排名 {hit_idx + 1}: 距离={hit['distance']:.4f}")
print(f" 标题: {hit['entity'].get('title', 'N/A')}")
print(f" 内容: {hit['entity'].get('content', 'N/A')[:100]}...")
print()
python
# 4. 执行混合搜索(一次性处理所有查询)
client = MilvusClient(uri="http://192.168.6.160:19530")
# 注意:AnnSearchRequest 的 data 参数可以传入多个向量
dense_search_req = AnnSearchRequest(
data=dense_vectors, # 传入所有稠密向量
anns_field="dense_vector",
param={"metric_type": "IP", "params": {"nprobe": 10}},
limit=5
)
sparse_search_req = AnnSearchRequest(
data=sparse_vectors, # 传入所有稀疏向量
anns_field="sparse_vector",
param={"metric_type": "IP"},
limit=5
)
results = client.hybrid_search(
collection_name="kb_chunks_v1",
reqs=[dense_search_req, sparse_search_req],
ranker=WeightedRanker(0.7, 0.3),
limit=5,
output_fields=["content", "title"]
)
# 结果按查询分组
for query_idx, query_results in enumerate(results):
print(f"\n查询 {query_idx + 1}: {query_texts[query_idx]}")
for hit in query_results:
print(f" 距离: {hit['distance']:.4f} | {hit['entity']}")
encode_queriesvsencode_documents:BGE-M3 采用非对称检索策略。查询文本用encode_queries,入库文档用encode_documents,两者会在文本前添加不同的指令前缀,让模型生成更适合匹配的向量。
9. 标量过滤查询
除了向量搜索,Milvus 也支持纯标量字段的过滤查询(类似 SQL 的 WHERE):
python
# 精确匹配
results = client.query(
collection_name="kb_chunks_v1",
filter='item_name == "Milvus"',
output_fields=["content", "title"]
)
# IN 查询
results = client.query(
collection_name="kb_chunks_v1",
filter='item_name in ["Milvus", "RAG", "LLM"]',
output_fields=["content", "title"]
)
# 带变量的表达式(更安全,避免注入)
expr = "item_name in {item_names}"
expr_params = {"item_names": ["Milvus", "RAG"]}
results = client.query(
collection_name="kb_chunks_v1",
filter=expr,
filter_params=expr_params,
output_fields=["content", "title"]
)
在搜索中使用过滤
过滤条件也可以用在向量搜索中,缩小搜索范围:
python
results = client.search(
collection_name="kb_chunks_v1",
data=[query_vector],
limit=5,
filter='item_name in {item_names}',
filter_params={"item_names": ["Milvus", "RAG"]},
output_fields=["content", "title"]
)
10. 删除与清理
python
# 按条件删除
client.delete(
collection_name="kb_chunks_v1",
filter='item_name == "Milvus"'
)
# 按 ID 删除
client.delete(
collection_name="kb_chunks_v1",
ids=[1, 2, 3]
)
# 删除整个集合
client.drop_collection("kb_chunks_v1")
11. 工程实践:客户端管理
11.1 完整的客户端管理器
项目中使用了基类 + 双重检查锁的模式,统一管理 Milvus、MinIO、MongoDB 等多种客户端:
python
import os
import threading
from typing import Optional
from pymilvus import MilvusClient
class BaseClientManager:
"""客户端管理器基类"""
@staticmethod
def _require_env(key: str) -> str:
"""读取环境变量,缺失则抛异常"""
value = os.getenv(key)
if not value:
raise EnvironmentError(f"缺少环境变量: {key}")
return value
@classmethod
def _get_or_create(cls, attr_name, lock, factory):
"""双重检查锁模板方法"""
instance = getattr(cls, attr_name, None)
if instance is not None:
return instance
with lock:
instance = getattr(cls, attr_name, None)
if instance is not None:
return instance
instance = factory()
setattr(cls, attr_name, instance)
return instance
class StorageClients(BaseClientManager):
_milvus_client: Optional[MilvusClient] = None
_milvus_lock = threading.Lock()
@classmethod
def get_milvus_client(cls) -> MilvusClient:
return cls._get_or_create(
"_milvus_client", cls._milvus_lock, cls._create_milvus_client
)
@classmethod
def _create_milvus_client(cls) -> MilvusClient:
milvus_uri = cls._require_env("MILVUS_URL")
return MilvusClient(milvus_uri)
11.2 使用方式
python
# 全局获取客户端(线程安全,自动复用)
client = StorageClients.get_milvus_client()
# 后续调用直接返回已有实例,不会重复创建
client2 = StorageClients.get_milvus_client()
assert client is client2 # True
12. 常见问题
Q1: dimension 不匹配会怎样?
创建集合时 dim 必须与 Embedding 模型的输出维度严格一致 。例如 BGE-M3 的稠密向量维度是 1024,则 dim=1024。不匹配会报错。
Q2: 搜索前需要 load() 吗?
使用 MilvusClient(新版 API)时,创建集合后会自动加载到内存,无需手动调用 load() 。如果使用旧版 Collection API,则需要手动加载。
Q3: AUTOINDEX 够用吗?
对于中小规模数据(百万级以下),AUTOINDEX 完全够用,Milvus 会自动选择最优索引。只有在对性能有极致要求时,才需要手动选择 HNSW 或 IVF_FLAT 等索引类型并调参。
Q4: 混合搜索的权重怎么调?
WeightedRanker(0.7, 0.3) 表示稠密向量权重 0.7、稀疏向量权重 0.3。一般建议:
- 语义理解为主:稠密权重高(0.7~0.8)
- 关键词匹配为主:稀疏权重高(0.6~0.7)
- 根据实际效果微调
Q5: 如何查看集合的统计信息?
python
# 查看集合中的实体数量
stats = client.get_collection_stats("kb_chunks_v1")
print(stats)
# 列出所有集合
collections = client.list_collections()
print(collections)
附录:完整流程速查
python
from pymilvus import MilvusClient, DataType, AnnSearchRequest, WeightedRanker
# 1. 连接
client = MilvusClient(uri="http://localhost:19530")
# 2. 创建集合(快捷方式)
client.create_collection(
collection_name="demo",
dimension=1024,
metric_type="COSINE"
)
# 3. 插入数据
client.insert("demo", data=[
{"id": 1, "vector": [0.1] * 1024, "text": "Hello Milvus"},
{"id": 2, "vector": [0.2] * 1024, "text": "向量数据库教程"},
])
# 4. 搜索
results = client.search(
"demo",
data=[[0.12] * 1024],
limit=2,
output_fields=["text"]
)
for hit in results[0]:
print(f"{hit['distance']:.4f} | {hit['entity']['text']}")
# 5. 清理
client.drop_collection("demo")