Milvus vs MySQL:向量数据库与关系型数据库深度对比

涵盖数据模型、存储引擎、索引类型、距离度量、代码实现与互补架构设计

目录

  1. 概述与定位差异
  2. 核心差异多维对比(数据模型 / 存储引擎 / 适用场景 / 查询方式)
  3. Milvus 索引类型详解(FLAT / IVF 系列 / HNSW / ANNOY / DiskANN 等)
  4. Milvus 距离度量方式(L2 / IP / COSINE / HAMMING / JACCARD)
  5. Milvus 完整代码实现示例(连接 → Schema → 索引 → 增删改查)
  6. 两者互补配合使用方案
  7. 选型决策建议

一、概述与定位差异

MySQL 是成熟的关系型数据库(RDBMS),以结构化表格存储数据,通过 SQL 进行精确的事务查询(OLTP),适合存储订单、用户、账户等强一致性业务数据。

Milvus 是开源的云原生向量数据库,专为 AI 时代的高维向量相似度检索而生,通过近似最近邻搜索(ANN)在海量向量中毫秒级找到”最相似”的结果,广泛用于推荐系统、图像/文本/语音检索、RAG 知识库等场景。

💡 一句话定位

MySQL 回答的是 “哪些数据满足这个条件”(精确匹配);Milvus 回答的是 “哪些数据和这条数据最像”(相似度匹配)。两者解决的是截然不同的问题,而非替代关系。


二、核心差异多维对比

2.1 总览对比表

对比维度 Milvus 向量数据库 MySQL 关系型数据库
设计目标 高维向量相似度检索(ANN) 结构化数据的事务处理(OLTP)
数据模型 Collection(集合)+ Entity,含向量字段与标量字段,支持动态 Schema Table(表)+ Row/Column,固定列 Schema,关系模型
存储引擎 计算存储分离:对象存储(MinIO/S3)+ 消息队列 + 计算节点 InnoDB(默认,B+树 + 行存储)/ MyISAM
索引结构 向量索引(HNSW/IVF/DiskANN)+ 标量索引(倒排/BITMAP) B+Tree、Hash、Full-text、空间索引
查询方式 向量相似度搜索(top-K 近邻)+ 标量过滤混合查询 SQL 结构化查询(SELECT/WHERE/JOIN/GROUP BY)
查询结果 近似结果(ANN),按相似度排序,召回率可调 精确结果,100% 匹配条件
事务支持 有限(无完整 ACID,最终一致性为主) 完整 ACID 事务、MVCC、行级锁
数据规模 亿级~百亿级向量 单表千万级(水平扩展需分库分表)
典型负载 高吞吐读、批量写、向量计算密集 高并发读写混合、事务密集

2.2 数据模型差异

方面 Milvus MySQL
基本单元 Collection(集合)→ Entity(实体) Table(表)→ Row(行)
字段类型 向量字段(FLOAT_VECTOR/BINARY_VECTOR)+ 标量字段(INT64/VARCHAR/JSON/ARRAY 等) INT/VARCHAR/TEXT/DECIMAL/DATETIME 等丰富的标量类型
Schema 灵活性 支持动态字段(Dynamic Field),可存储未预定义的 JSON 字段 固定列定义,加列需 ALTER TABLE
主键 支持 INT64 或 VARCHAR 主键,可自增 主键、唯一键、自增列
关系关联 无外键、无 JOIN,通过标量过滤做简单关联 外键约束、JOIN、关系完整性
向量化 原生支持,向量是一等公民 不支持(需外部计算)

2.3 存储引擎差异

Milvus 存储架构(云原生 / 计算存储分离)

  • 对象存储(MinIO / AWS S3 / Azure Blob):存放日志、向量数据段(segment)、索引文件,成本极低、容量近乎无限。
  • 消息队列(Pulsar / Kafka / RocksMQ):实现写入流的 WAL(预写日志),保证数据可靠传输到计算节点。
  • 计算节点:Query Node(查询)、Data Node(写入)、Index Node(构建索引)分离部署,各自独立扩缩容。
  • 元数据存储(etcd):存储 Collection Schema、节点拓扑、分段信息等元数据。

这种架构使 Milvus 可水平扩展到百亿级向量,存储与计算可独立扩容,但写入延迟相对 MySQL 较高(毫秒~秒级,最终一致性)。

MySQL 存储架构(InnoDB)

  • Buffer Pool:内存缓存热数据页,减少磁盘 IO。
  • B+Tree 聚簇索引:数据按主键聚簇存储,叶子节点即完整行数据。
  • Redo Log / Undo Log / Binlog:保证 ACID 事务、崩溃恢复、主从复制。
  • 行存储 + 行级锁 + MVCC:支持高并发事务,读写冲突可控。

MySQL 在单机事务性能上远超 Milvus,但扩展性受限于单机存储和分库分表方案的复杂度。

2.4 查询方式差异(核心)

维度 Milvus(向量相似度搜索) MySQL(结构化查询)
查询语义 “找最相似的 top-K 条” “找满足条件的所有行”
查询输入 一个查询向量 + top-K + 过滤表达式 SQL 语句(WHERE 条件)
匹配方式 计算向量距离,近似检索(ANN) B+Tree 精确匹配 / 范围扫描
结果排序 按相似度(距离)从近到远排序 按 ORDER BY 指定字段排序
结果确定性 近似结果,召回率与延迟可权衡(调参) 100% 确定结果,符合条件必返回
典型查询 search(query_vector, top_k=10) SELECT * FROM t WHERE id = 1
混合查询 支持”向量相似 + 标量过滤”组合(如”找相似图片且价格<100”) 支持多条件 WHERE + JOIN

💡 关键认知

MySQL 的查询是”精确条件 → 全量命中”,无法回答”语义相似”问题。例如搜”红色的花”,MySQL 只能匹配 color='红' 的精确字段,无法识别图片中”红色花朵”的视觉相似性——而这正是 Milvus 的核心能力。


三、Milvus 索引类型详解

Milvus 针对浮点向量(FLOAT_VECTOR)和二值向量(BINARY_VECTOR)分别提供不同的索引类型。以下按”暴力搜索 → 倒排量化类 → 图索引类 → 磁盘类 → 二值类”逐一说明。

3.1 浮点向量索引


① FLAT(暴力搜索) 精确

  • 原理:线性扫描,逐一计算查询向量与库中所有向量的距离,不做任何近似。
  • 召回率:100%(精确结果)
  • 查询速度:慢,O(n) 复杂度

✓ 优点:结果完全精确,无需训练/构建索引,无精度损失。

✗ 缺点:数据量大时查询极慢,内存占用高。

适用场景:数据量小(百万级以下)、要求 100% 召回率、用于评估其他 ANN 索引效果的基准。


② IVF_FLAT(倒排 + 簇内暴力) 均衡

  • 原理:先用 K-means 将向量聚类为 nlist 个簇(倒排文件),查询时只在最近的 nprobe 个簇内做暴力搜索。
  • 关键参数nlist(簇数,建议 4×sqrt(n))、nprobe(探测簇数,越大召回越高、速度越慢)
  • 召回率:取决于 nprobe,通常 90%~99%

✓ 优点:查询速度与精度平衡良好,无量化精度损失,实现成熟。

✗ 缺点:需训练,内存占用与原始数据相当,大规模数据下仍偏高。

适用场景:中大规模数据(百万~千万),对精度有较高要求,内存充足。


③ IVF_SQ8(倒排 + 标量量化) 省内存

  • 原理:在 IVF 基础上,对每个向量做标量量化(Scalar Quantization),将 float32(32 位)压缩为 int8(8 位),内存减少约 75%。
  • 关键参数:同 IVF_FLAT(nlist / nprobe)
  • 召回率:略低于 IVF_FLAT(量化引入轻微误差)

✓ 优点:内存占用大幅降低(约 1/4),查询速度更快,适合内存受限场景。

✗ 缺点:量化带来轻微精度损失,需训练。

适用场景:大规模数据且内存有限,可接受小幅精度损失。


④ IVF_PQ(倒排 + 乘积量化) 极致压缩

  • 原理:在 IVF 基础上做乘积量化(Product Quantization):将向量切分为 m 段,每段独立做 K-means 量化为码本,最终每条向量用一组短编码表示。
  • 关键参数m(分段数)、nbits(每段编码位数)、nlist/nprobe
  • 压缩比:极高,可压缩至原数据的 1/16 ~ 1/32

✓ 优点:存储压缩极致,适合超大规模数据,查询速度快。

✗ 缺点:精度损失较大,不适合对召回率要求极高的场景,需训练。

适用场景:十亿级以上超大规模数据,优先考虑存储成本,可接受一定精度损失(如粗排召回阶段)。


⑤ HNSW(分层可导航小世界图) 性能最佳

  • 原理:构建多层近邻图:顶层稀疏用于快速导航(长距离跳跃),底层稠密用于精确搜索(局部细化)。查询时从顶层逐层向下贪心搜索。
  • 关键参数M(每层最大邻居数,建议 16~64)、efConstruction(建图候选集大小)、ef(查询候选集大小,越大越准)
  • 召回率:高(通常 95%+,可调到 99%+)

✓ 优点:查询速度极快、召回率高,无需训练,支持动态增删。

✗ 缺点:内存占用大(存储图结构),构建索引较慢,不适合磁盘场景。

适用场景:对查询性能和召回率要求都高的场景(推荐系统、语义搜索),实际项目中最常用的索引


⑥ ANNOY(基于树的 ANN) 静态

  • 原理:随机选取两点,取垂直平分面将空间二分(构建二叉树),递归划分直到叶子节点。构建多棵这样的树组成森林,查询时聚合结果。
  • 关键参数n_trees(树的数量)、search_k(搜索节点数)

✓ 优点:支持内存映射文件(mmap),内存友好,静态数据查询稳定。

✗ 缺点不支持动态删除,更新需重建索引,召回率不如 HNSW。

适用场景:数据相对静态(一次写入多次查询)、只读检索、内存有限的环境。


⑦ DiskANN(基于磁盘的图索引) 超大规模

  • 原理:基于 Vamana 图算法构建近邻图,索引存储在磁盘(SSD)上,查询时按需加载图节点到内存,用缓存加速。
  • 关键参数search_list_size(搜索候选集大小)
  • 特点:专为超大规模、内存装不下的场景设计

✓ 优点:突破内存限制,支持百亿级向量,成本远低于全内存方案。

✗ 缺点:查询延迟略高于纯内存索引(毫秒级),依赖 SSD 性能。

适用场景:数据量超出内存容量、成本敏感的超大规模检索(如全网图像去重、大规模指纹库)。


⑧ GPU 索引(GPU_CAGRA / GPU_IVF_FLAT / GPU_IVF_PQ) GPU 加速

  • 原理:利用 GPU 并行计算能力加速向量检索,CAGRA 是 NVIDIA 提出的基于图的 GPU ANN 算法。
  • 关键参数:各算法不同,CAGRA 需 intermediate_graph_degree/graph_degree

✓ 优点:吞吐量极高,大批量查询场景下延迟显著低于 CPU。

✗ 缺点:需 GPU 硬件,成本高,单条查询的延迟优势不明显。

适用场景:高并发批量查询、已有 GPU 集群、对吞吐量要求极致的场景。


⑨ SCANN(Google 量化感知搜索) 高精度

  • 原理:Google 提出的各向异性量化(Anisotropic Quantization)+ 倒排索引,针对向量分布方向优化量化误差。
  • 关键参数with_raw_data(是否保留原始向量)

✓ 优点:在同等压缩比下召回率优于 IVF_PQ,精度表现优秀。

✗ 缺点:参数调优相对复杂,需训练。

适用场景:对召回率和压缩比都有较高要求的中大规模场景。


3.2 二值向量索引

索引 原理 适用场景
BIN_FLAT 二值向量的暴力搜索,用汉明距离计算 小规模二值向量,精确匹配
BIN_IVF_FLAT 二值向量的倒排索引,聚类后在簇内暴力搜索 中大规模二值向量

二值向量通常由哈希算法(如 LSH)生成,占用空间小但信息量有限,适合对精度要求不高的场景。

3.3 索引选型速查表

需求 推荐索引 理由
数据量小,要求 100% 精确 FLAT 无近似误差
通用场景,性能与精度平衡 HNSW 速度快、召回高、无需训练(最常用)
内存受限的中大规模数据 IVF_SQ8 压缩 75% 内存
超大规模,优先省存储 IVF_PQ 极致压缩
数据超出内存,成本敏感 DiskANN 磁盘存储,突破内存限制
静态数据,只读查询 ANNOY 支持 mmap,内存友好
高并发批量查询,有 GPU GPU_CAGRA GPU 并行加速

四、Milvus 距离度量方式

距离度量决定了”相似”的计算方式,直接影响检索效果。索引类型必须与距离度量匹配,且创建索引后不可更改。

度量 公式含义 值范围 相似判定 应用场景
L2(欧氏距离) 向量各维度差值的平方和再开方 [0, +∞) 值越小越相似 图像特征、通用嵌入向量;向量未归一化时使用
IP(内积) 两向量点积 a·b = Σ(aᵢ×bᵢ) (-∞, +∞) 值越大越相似 向量已归一化时等价于余弦相似度;推荐系统、最大内积搜索(MIPS)
COSINE(余弦相似度) cos(θ) = (a·b) / (|a|×|b|) [-1, 1] 值越大越相似 文本语义相似度、自然语言检索;关注方向而非模长
HAMMING(汉明距离) 两二值向量不同位的个数 [0, 维度] 值越小越相似 二值向量检索、图像感知哈希去重
JACCARD(杰卡德距离) 1 - |A∩B| / |A∪B| [0, 1] 值越小越相似 集合相似度、词袋模型、二值向量

💡 选型建议

  • 文本语义检索(NLP):优先用 COSINE,因为文本嵌入通常关注方向相似性。若向量已归一化,IPCOSINE 结果一致但计算更快。
  • 图像/音频特征:常用 L2,因为这些特征向量通常未归一化。
  • 推荐系统(MIPS):用 IP,直接最大化内积得分。
  • 二值向量去重:用 HAMMING,计算极快。

五、Milvus 完整代码实现示例

以下基于 pymilvus 2.x(新版 SDK,使用 MilvusClient 统一接口),完整演示从连接到检索的全流程。

5.1 环境准备与连接建立

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 安装:pip install pymilvus
from pymilvus import MilvusClient, DataType
import numpy as np

# 连接 Milvus(以下任选其一)
# 方式1:连接本地 Milvus Standalone
client = MilvusClient(uri="http://localhost:19530")

# 方式2:使用 Milvus Lite(轻量版,数据存本地文件,适合开发测试)
# client = MilvusClient("./milvus_demo.db")

# 方式3:连接 Zilliz Cloud(托管云服务)
# client = MilvusClient(
# uri="https://xxx.zillizcloud.com",
# token="user:password"
# )

print("Milvus 连接成功:", client.list_collections())

5.2 定义 Collection Schema(集合结构)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
COLLECTION_NAME = "product_vectors"  # 商品向量集合

# 若集合已存在则先删除(便于重复运行演示)
if client.has_collection(COLLECTION_NAME):
client.drop_collection(COLLECTION_NAME)

# 创建 Schema
schema = MilvusClient.create_schema(
auto_id=False, # 不自动生成主键(我们手动指定)
enable_dynamic_field=True # 启用动态字段,可存储未预定义的 JSON 字段
)

# 添加字段:主键、向量、标量字段
schema.add_field("id", DataType.VARCHAR, max_length=64, is_primary=True) # 主键:商品ID
schema.add_field("embedding", DataType.FLOAT_VECTOR, dim=512) # 向量:512维商品图特征
schema.add_field("name", DataType.VARCHAR, max_length=128) # 商品名
schema.add_field("price", DataType.FLOAT) # 价格
schema.add_field("category", DataType.VARCHAR, max_length=32) # 分类
schema.add_field("tags", DataType.ARRAY, element_type=DataType.VARCHAR,
max_capacity=10, max_length=32) # 标签数组

print("Schema 定义完成:")
print(schema)

5.3 创建索引

⚠️ 重要:索引创建顺序

新版 pymilvus 中,必须先创建 Collection(含索引参数)再加载数据。向量和标量字段分别建索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 向量字段索引:使用 HNSW(推荐通用选择)
index_params = client.prepare_index_params()

index_params.add_index(
field_name="embedding", # 对向量字段建索引
index_type="HNSW", # 索引类型
metric_type="COSINE", # 距离度量:余弦相似度
params={
"M": 16, # 每层最大邻居数,影响图密度与内存
"efConstruction": 200 # 建图时候选集大小,越大建得越精细
}
)

# 标量字段索引:加速过滤查询(Milvus 2.x 支持标量倒排索引)
index_params.add_index(
field_name="category",
index_type="", # 空字符串表示使用默认标量索引
index_name="category_idx"
)

index_params.add_index(
field_name="price",
index_type="", # 数值字段标量索引
index_name="price_idx"
)

print("索引参数准备完成:")
print(index_params)

5.4 创建 Collection 并加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建集合:指定 schema 和索引参数
client.create_collection(
collection_name=COLLECTION_NAME,
schema=schema,
index_params=index_params
)

# 加载集合到内存(查询前必须 load,写入可不必)
client.load_collection(COLLECTION_NAME)
print(f"集合 '{COLLECTION_NAME}' 已创建并加载到内存")

# 查看集合信息
desc = client.describe_collection(COLLECTION_NAME)
print(f"集合信息: {desc}")

5.5 数据插入(Insert)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 模拟商品向量数据
def generate_mock_products(n=1000):
"""生成 n 条模拟商品数据"""
np.random.seed(42)
data = []
for i in range(n):
item = {
"id": f"P{i:06d}",
"embedding": np.random.randn(512).tolist(), # 512维向量
"name": f"商品_{i}",
"price": round(np.random.uniform(10, 999), 2),
"category": np.random.choice(["手机", "电脑", "服饰", "食品", "图书"]),
"tags": np.random.choice(["热销", "新品", "折扣", "限量"],
size=np.random.randint(1, 4)).tolist()
}
data.append(item)
return data

# 生成数据
products = generate_mock_products(1000)

# 分批插入(建议每批 1000~5000 条)
BATCH_SIZE = 500
for i in range(0, len(products), BATCH_SIZE):
batch = products[i:i+BATCH_SIZE]
result = client.insert(
collection_name=COLLECTION_NAME,
data=batch
)
print(f"第 {i//BATCH_SIZE + 1} 批插入完成,影响行数: {result['insert_count']}")

# 查看集合行数
stats = client.get_collection_stats(COLLECTION_NAME)
print(f"集合当前数据量: {stats}")

5.6 向量检索(Similarity Search)—— 核心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 生成一个查询向量(模拟用户上传图片的特征)
query_vector = np.random.randn(512).tolist()

# ========== 基础向量检索:找最相似的 top-K ==========
results = client.search(
collection_name=COLLECTION_NAME,
data=[query_vector], # 注意是列表的列表(支持批量查询)
anns_field="embedding", # 指定向量字段
limit=10, # 返回 top-10
output_fields=["id", "name", "price", "category"] # 返回这些标量字段
)

print("=== 基础检索结果(最相似的 10 个商品)===")
for hit in results[0]:
entity = hit["entity"]
print(f" ID={entity['id']} | 相似度={hit['distance']:.4f} | "
f"{entity['name']} | ¥{entity['price']} | {entity['category']}")

# ========== 混合检索:向量相似 + 标量过滤 ==========
# 场景:找相似商品,且价格低于 500、属于"手机"或"电脑"类
filter_expr = 'price < 500 and category in ["手机", "电脑"]'

results_filtered = client.search(
collection_name=COLLECTION_NAME,
data=[query_vector],
anns_field="embedding",
limit=10,
filter=filter_expr, # 标量过滤条件(Milvus 表达式语法)
output_fields=["id", "name", "price", "category", "tags"]
)

print("\n=== 混合检索结果(相似 + 价格<500 + 手机/电脑)===")
for hit in results_filtered[0]:
entity = hit["entity"]
print(f" ID={entity['id']} | 相似度={hit['distance']:.4f} | "
f"{entity['name']} | ¥{entity['price']} | {entity['category']} | {entity.get('tags')}")

5.7 标量查询(Query,非向量)

1
2
3
4
5
6
7
8
9
10
11
12
# 直接按标量条件查询(不涉及向量相似度)
# 场景:查所有"折扣"标签且价格<200 的商品
query_results = client.query(
collection_name=COLLECTION_NAME,
filter='tags in ["折扣"] and price < 200',
output_fields=["id", "name", "price", "category"],
limit=5
)

print("=== 标量查询结果 ===")
for item in query_results:
print(f" {item['id']} | {item['name']} | ¥{item['price']} | {item['category']}")

5.8 数据更新(Update / Upsert)

⚠️ Milvus 的”更新”机制

Milvus 没有传统 SQL 的 UPDATE SET 语句。更新 = Upsert(Insert + Update 的合体):主键已存在则覆盖,不存在则插入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Upsert:更新商品 P000001 的信息(主键相同会覆盖原数据)
updated_data = [{
"id": "P000001",
"embedding": np.random.randn(512).tolist(), # 更新向量
"name": "商品_1_已更新",
"price": 888.88,
"category": "手机",
"tags": ["限量", "热销"]
}]

upsert_result = client.upsert(
collection_name=COLLECTION_NAME,
data=updated_data
)
print(f"Upsert 完成,影响行数: {upsert_result['upsert_count']}")

# 验证更新结果
verify = client.query(
collection_name=COLLECTION_NAME,
filter='id == "P000001"',
output_fields=["id", "name", "price"]
)
print(f"更新后数据: {verify}")

5.9 数据删除(Delete)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 按主键删除单条
delete_result = client.delete(
collection_name=COLLECTION_NAME,
filter='id == "P000002"' # 删除主键为 P000002 的记录
)
print(f"删除 P000002 完成,删除数: {delete_result['delete_count']}")

# 按条件批量删除(Milvus 2.x 支持)
batch_delete = client.delete(
collection_name=COLLECTION_NAME,
filter='price > 900' # 删除所有价格 > 900 的商品
)
print(f"批量删除完成,删除数: {batch_delete['delete_count']}")

# 验证删除后的数据量
stats_after = client.get_collection_stats(COLLECTION_NAME)
print(f"删除后集合数据量: {stats_after}")

5.10 批量向量检索(多查询并行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 同时查询多个向量(一次性传多个查询向量)
query_vectors = [np.random.randn(512).tolist() for _ in range(5)]

batch_results = client.search(
collection_name=COLLECTION_NAME,
data=query_vectors, # 5个查询向量
anns_field="embedding",
limit=3,
output_fields=["id", "name"]
)

print("=== 批量检索结果(5个查询,各返回 top-3)===")
for i, res in enumerate(batch_results):
print(f"\n查询 #{i+1} 的结果:")
for hit in res:
print(f" {hit['entity']['id']} (相似度={hit['distance']:.4f}) - {hit['entity']['name']}")

5.11 范围搜索与分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 范围搜索(Range Search):返回相似度在指定范围内的结果
range_results = client.search(
collection_name=COLLECTION_NAME,
data=[query_vector],
anns_field="embedding",
limit=20,
search_params={
"metric_type": "COSINE",
"params": {
"ef": 100, # HNSW 查询候选集大小
"radius": 0.3, # 相似度下限(余弦相似度)
"range_filter": 0.9 # 相似度上限
}
},
output_fields=["id", "name"]
)
print(f"范围搜索返回 {len(range_results[0])} 条结果")

# ========== 分区操作 ==========
# 创建分区(按业务维度隔离数据)
PARTITION = "electronics"
client.create_partition(
collection_name=COLLECTION_NAME,
partition_name=PARTITION
)

# 向指定分区插入数据
client.insert(
collection_name=COLLECTION_NAME,
data=[{
"id": "E001",
"embedding": np.random.randn(512).tolist(),
"name": "电子产品_1",
"price": 1299.0,
"category": "电脑",
"tags": ["新品"]
}],
partition_name=PARTITION
)

# 仅在指定分区中搜索
results_in_partition = client.search(
collection_name=COLLECTION_NAME,
data=[query_vector],
anns_field="embedding",
limit=5,
partition_names=[PARTITION], # 限定分区
output_fields=["id", "name"]
)
print(f"分区搜索返回 {len(results_in_partition[0])} 条结果")

5.12 资源清理

1
2
3
4
5
6
7
8
9
10
11
# 释放集合内存
client.release_collection(COLLECTION_NAME)
print("集合已从内存释放")

# 删除集合
client.drop_collection(COLLECTION_NAME)
print(f"集合 '{COLLECTION_NAME}' 已删除")

# 关闭连接
client.close()
print("连接已关闭")

六、两者互补配合使用方案

Milvus 和 MySQL 在实际项目中是互补而非替代关系。典型架构如下:

6.1 典型互补架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────┐
│ 应用业务层 │
│ (商品搜索 / 推荐系统 / 图像检索) │
└──────────────┬──────────────────────┬────────────────────┘
│ │
┌───────▼────────┐ ┌────────▼────────┐
│ MySQL │ │ Milvus │
│ ───────────── │ │ ───────────── │
│ • 商品完整信息 │ │ • 商品图片特征 │
│ (名称/价格/ │ │ (512维向量) │
│ 库存/详情) │ │ • 向量相似度 │
│ • 用户账户/订单 │ │ 检索 (ANN) │
│ • 业务事务处理 │ │ • 标量过滤 │
│ • 关联查询 JOIN │ │ • top-K 排序 │
└───────┬────────┘ └────────┬────────┘
│ │
└──────────┬───────────┘

┌────────▼────────┐
│ 特征提取服务 │
│ (CLIP/ResNet/ │
│ BERT 等模型) │
└─────────────────┘

6.2 协作流程(以商品搜索为例)

步骤 MySQL 负责 Milvus 负责
① 数据写入 存储商品完整业务字段(名称、价格、库存、SKU、详情) 存储商品图片/文本的特征向量 + 商品 ID
② 在线检索 接收查询向量,返回 top-K 相似商品的 ID 列表
③ 详情补全 根据返回的 ID 批量查询完整商品信息
④ 事务操作 下单、扣库存、支付等强一致性事务
⑤ 数据同步 商品上架/下架时触发 同步更新对应的向量数据(新增/删除/upsert)

6.3 具体代码示例(双库协作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import pymysql
from pymilvus import MilvusClient
from feature_extractor import extract_image_embedding # 假设的特征提取服务

# ========== 初始化两个数据库连接 ==========
mysql_conn = pymysql.connect(host="localhost", user="root",
password="***", database="shop", charset="utf8mb4")
milvus_client = MilvusClient(uri="http://localhost:19530")

COLLECTION = "product_vectors"

# ========== 场景1:商品上架 —— 双写同步 ==========
def on_product_upload(product_id, image_url, name, price, category):
"""商品上架时,同时写入 MySQL 和 Milvus"""

# 1. MySQL 写入业务数据
with mysql_conn.cursor() as cursor:
sql = """INSERT INTO products (id, name, price, category, image_url, status)
VALUES (%s, %s, %s, %s, %s, 'online')"""
cursor.execute(sql, (product_id, name, price, category, image_url))
mysql_conn.commit()

# 2. 提取图片特征向量
embedding = extract_image_embedding(image_url) # 返回 512维向量

# 3. Milvus 写入向量数据(仅存 ID + 向量 + 必要过滤字段)
milvus_client.insert(
collection_name=COLLECTION,
data=[{
"id": product_id,
"embedding": embedding,
"category": category,
"price": price
}]
)
print(f"商品 {product_id} 上架完成(MySQL + Milvus 同步)")


# ========== 场景2:以图搜商品 —— Milvus 检索 + MySQL 补全 ==========
def search_by_image(query_image_url, top_k=10, max_price=None):
"""用户上传图片,搜索相似商品"""

# 1. 提取查询图片的特征向量
query_vector = extract_image_embedding(query_image_url)

# 2. Milvus 向量相似度检索,返回 top-K 商品 ID
filter_expr = f"price < {max_price}" if max_price else ""
search_results = milvus_client.search(
collection_name=COLLECTION,
data=[query_vector],
anns_field="embedding",
limit=top_k,
filter=filter_expr,
output_fields=["id"] # 只取 ID,详情从 MySQL 查
)

# 3. 收集相似商品 ID
product_ids = [hit["entity"]["id"] for hit in search_results[0]]
if not product_ids:
return []

# 4. MySQL 批量查询完整商品信息
with mysql_conn.cursor(pymysql.cursors.DictCursor) as cursor:
placeholders = ",".join(["%s"] * len(product_ids))
sql = f"""SELECT id, name, price, category, image_url, stock
FROM products WHERE id IN ({placeholders})"""
cursor.execute(sql, product_ids)
products = {row["id"]: row for row in cursor.fetchall()}

# 5. 按 Milvus 的相似度排序组装结果
results = []
for hit in search_results[0]:
pid = hit["entity"]["id"]
if pid in products:
results.append({
**products[pid],
"similarity": hit["distance"] # 相似度得分
})
return results


# ========== 场景3:商品下架 —— 双删同步 ==========
def on_product_offline(product_id):
"""商品下架时,双库同步删除"""

# 1. MySQL 软删除(更新状态,保留历史记录)
with mysql_conn.cursor() as cursor:
cursor.execute(
"UPDATE products SET status='offline' WHERE id=%s",
(product_id,)
)
mysql_conn.commit()

# 2. Milvus 硬删除(向量不再参与检索)
milvus_client.delete(
collection_name=COLLECTION,
filter=f'id == "{product_id}"'
)
print(f"商品 {product_id} 已下架(MySQL 软删 + Milvus 硬删)")

6.4 职责分工总结

职责 由谁承担 原因
用户/订单/支付事务 MySQL 需要 ACID 事务、强一致性
商品完整业务信息 MySQL 结构化字段、关联查询、统计
向量相似度检索 Milvus MySQL 无法高效做 ANN
语义/图像/文本检索 Milvus 理解”相似”而非精确匹配
推荐系统召回 Milvus 毫秒级从百万向量中找 top-K
数据一致性保障 两者协同 MySQL 为主,Milvus 异步同步

七、选型决策建议

你的场景 选择
需要事务、强一致性、结构化精确查询 MySQL
需要”语义相似”检索(图像/文本/音频/推荐) Milvus
数据量超千万,需要水平扩展检索能力 Milvus
RAG 知识库 / 语义问答系统 Milvus + LLM
商品搜索(以图搜物 + 详情展示) 两者互补
内容去重(感知哈希) Milvus(二值向量)
传统报表统计、复杂 JOIN MySQL

核心结论

大多数现代 AI 应用都需要双库并存:MySQL 管理结构化业务数据与事务,Milvus 负责向量化相似度检索。两者通过主键(如商品 ID)关联,各司其职、协同工作。理解各自的能力边界,是构建高性能 AI 应用的关键。


注:本文档基于 Milvus 2.x(pymilvus 2.3+)与 MySQL 8.0 编写。不同版本 API 可能有差异,请以官方文档为准。