Milvus vs MySQL:向量数据库与关系型数据库深度对比
2026-03-18
涵盖数据模型、存储引擎、索引类型、距离度量、代码实现与互补架构设计
目录
概述与定位差异
核心差异多维对比(数据模型 / 存储引擎 / 适用场景 / 查询方式)
Milvus 索引类型详解(FLAT / IVF 系列 / HNSW / ANNOY / DiskANN 等)
Milvus 距离度量方式(L2 / IP / COSINE / HAMMING / JACCARD)
Milvus 完整代码实现示例(连接 → Schema → 索引 → 增删改查)
两者互补配合使用方案
选型决策建议
一、概述与定位差异 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,因为文本嵌入通常关注方向相似性。若向量已归一化,IP 与 COSINE 结果一致但计算更快。
图像/音频特征 :常用 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 from pymilvus import MilvusClient, DataTypeimport numpy as npclient = MilvusClient(uri="http://localhost:19530" ) 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 = MilvusClient.create_schema( auto_id=False , enable_dynamic_field=True ) schema.add_field("id" , DataType.VARCHAR, max_length=64 , is_primary=True ) schema.add_field("embedding" , DataType.FLOAT_VECTOR, dim=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 index_params = client.prepare_index_params() index_params.add_index( field_name="embedding" , index_type="HNSW" , metric_type="COSINE" , params={ "M" : 16 , "efConstruction" : 200 } ) 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 client.create_collection( collection_name=COLLECTION_NAME, schema=schema, index_params=index_params ) 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(), "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 ) 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() results = client.search( collection_name=COLLECTION_NAME, data=[query_vector], anns_field="embedding" , limit=10 , output_fields=["id" , "name" , "price" , "category" ] ) print ("=== 基础检索结果(最相似的 10 个商品)===" )for hit in results[0 ]: entity = hit["entity" ] print (f" ID={entity['id' ]} | 相似度={hit['distance' ]:.4 f} | " f"{entity['name' ]} | ¥{entity['price' ]} | {entity['category' ]} " ) 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, 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' ]:.4 f} | " 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 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 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"' ) print (f"删除 P000002 完成,删除数: {delete_result['delete_count' ]} " )batch_delete = client.delete( collection_name=COLLECTION_NAME, filter ='price > 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, 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' ]:.4 f} ) - {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_results = client.search( collection_name=COLLECTION_NAME, data=[query_vector], anns_field="embedding" , limit=20 , search_params={ "metric_type" : "COSINE" , "params" : { "ef" : 100 , "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 pymysqlfrom pymilvus import MilvusClientfrom 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" def on_product_upload (product_id, image_url, name, price, category ): """商品上架时,同时写入 MySQL 和 Milvus""" 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() embedding = extract_image_embedding(image_url) milvus_client.insert( collection_name=COLLECTION, data=[{ "id" : product_id, "embedding" : embedding, "category" : category, "price" : price }] ) print (f"商品 {product_id} 上架完成(MySQL + Milvus 同步)" ) def search_by_image (query_image_url, top_k=10 , max_price=None ): """用户上传图片,搜索相似商品""" query_vector = extract_image_embedding(query_image_url) 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" ] ) product_ids = [hit["entity" ]["id" ] for hit in search_results[0 ]] if not product_ids: return [] 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()} results = [] for hit in search_results[0 ]: pid = hit["entity" ]["id" ] if pid in products: results.append({ **products[pid], "similarity" : hit["distance" ] }) return results def on_product_offline (product_id ): """商品下架时,双库同步删除""" with mysql_conn.cursor() as cursor: cursor.execute( "UPDATE products SET status='offline' WHERE id=%s" , (product_id,) ) mysql_conn.commit() 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 可能有差异,请以官方文档为准。