☰
从零入门Milvus向量数据库:核心概念与相似度检索实战
2026/10/2 18:10:22 网站建设 项目流程

1. 从零认识 Milvus:它到底解决了什么问题

如果你接触过推荐系统、以图搜图、智能问答这些场景,大概率听说过 Milvus 这个名字。我第一次看到它的时候,心里想的是:这不就是个向量数据库吗,和 Elasticsearch 有什么区别?后来真正上手做项目才发现,这个判断太天真了。

Milvus 是一个开源的分布式向量数据库,专门用来存储、索引和管理海量高维向量数据。所谓向量,你可以把它理解成一段由浮点数组成的数组,比如[0.12, 0.54, 0.87, ...]。在推荐系统里,用户的点击行为、商品的特征、图片的像素经过模型处理后,都会变成这样的向量。Milvus 做的事就是把这些向量存下来,然后在你给出一条新向量时,快速找出和它“最像”的那一批。

举个生活化的例子:你有一万张照片,每张照片都被一个模型压缩成了一个 512 维的向量。现在你有了一张新照片,想知道图库里有没有相似度高的图。如果用穷举法,一万次计算可能几秒钟就能跑完,看起来还不算慢。但如果图库扩大到一亿张呢?穷举法大概要算到天荒地老。Milvus 的价值就在这里,它能通过索引算法把这种搜索从秒级甚至分钟级压缩到毫秒级,这是传统数据库完全做不到的。

适合谁来学?我觉得三类人最需要。第一类是做 AI 应用的工程师,比如你在做 RAG(检索增强生成)或者知识库问答,Milvus 几乎是标配;第二类是推荐系统、图片搜索、音频检索相关的后端开发者;第三类是自己想折腾点东西的技术爱好者,比如给个人相册做个以图搜图的小工具。这篇文章我会从安装、启动、核心概念到实操代码,完整走一遍 Milvus 的常用流程。

2. Milvus 的定位与选型思考:为什么不是 ES,也不是 MySQL

很多人第一次接触向量检索,会习惯性地想到 Elasticsearch。ES 有 dense_vector 字段,也能做 KNN 搜索,为什么还要单独搞一个 Milvus?这个问题我在选型时纠结了很久,实际对比后结论很清楚。

ES 的向量检索能力属于“附加功能”,它本身的强项是全文检索和日志分析。向量索引要占用大量内存,如果数据量到了几百万条以上,ES 的查询性能下降非常快,而且索引构建时间很长。Milvus 从底层就是为向量设计的,支持多种索引类型(HNSW、IVF_FLAT、IVF_PQ、SCANN 等),在召回率、延迟和吞吐量上做了大量优化。简单说,如果向量是主要业务,用 Milvus;如果向量只是辅助,数据量也不大,ES 凑合能用。

MySQL 这类关系型数据库就更不用提了。它们支持的最多是暴力计算,数据量到十万级就开始吃力,更别提向量索引的构建和维护。当然,你也可以在 MySQL 里存向量字段,再用外部工具做 ANN(近似最近邻)搜索,但这就引入了额外的同步和一致性问题。Milvus 的定位就是把向量存、索引、检索一条链路都包揽下来。

再聊一个容易被忽视的点:Milvus 不只是“快”,它还有数据模型和元数据管理。每条向量可以带上多个标量字段(比如图片的 ID、类别、时间戳),查询时可以先按标量过滤再向量检索,这种 mixed query 能力在实际业务里几乎天天用到。

2.1 单机版与分布式版的区别

Milvus 有两种形态:Milvus Lite、单机版(Standalone)和分布式版(Cluster)。这里说的重点是单机版和分布式的选择。

单机版适合数据量在千万级以下、查询 QPS 要求不高的场景,部署简单,一个 Docker Compose 文件就能起来。分布式版适合数据量达到亿级、要求高可用和多副本的场景,组件包括 rootcoord、datacoord、querycoord、worker 节点等等,部署复杂度直线上升。

我个人的建议是:一开始就用单机版做原型验证,数据量真的涨上去了,再平滑迁移到分布式。Milvus 的 API 在单机和分布式之间是兼容的,这意味着切换成本并不高。

3. 安装 Milvus:Mac 上用 Docker,Linux 服务器上的完整流程

Milvus 的安装方式有好几种,官方推荐的是 Docker Compose。这里有大量初学者容易踩坑的地方,我把自己在 Mac 和 Linux 服务器上的安装过程完整记录下来。

3.1 Mac 上使用 Docker 安装 Milvus

在 Mac 上装 Milvus,本质就是跑几个 Docker 容器。Milvus 依赖 etcd(元数据存储)和 MinIO(对象存储),Docker Compose 会把这些组件一起拉起来。

首先是准备工作,确保你的 Mac 上已经装好了 Docker Desktop。这里有个关键步骤:Milvus 默认的 Docker Compose 文件要求 Docker 有至少 4GB 内存,如果 Docker Desktop 的默认内存配额不够,起容器后 etcd 会反复报错。我遇到过的最典型错误是etcdserver: request timed out,十有八九是内存不足。

具体操作如下:

  1. 从 GitHub 拉取仓库:
git clone https://github.com/milvus-io/milvus.git cd milvus/scripts
  1. 执行安装脚本:
./standalone_embed.sh start

这个命令会启动 Milvus 单机版,包括 etcd、MinIO 和 Milvus 三个容器。如果你不想用脚本,也可以直接用 Docker Compose:

curl -L https://github.com/milvus-io/milvus/releases/download/v2.4.9/milvus-standalone-docker-compose.yml -o docker-compose.yml docker compose up -d

等容器全部启动后,验证服务是否正常:

docker ps

你会看到三个容器在运行。如果一切正常,Milvus 默认会在本机的19530端口上提供服务。

3.2 Linux 服务器上的安装

Linux 服务器上的安装和 Mac 基本一样,只是有些前置检查。我用的是一台 4 核 16G 的云服务器,安装过程很顺利,但有几个细节容易被忽略。

第一,检查防火墙。如果服务器有安全组策略,记得放行 19530 端口和 9091 端口(监控端口)。我一开始没放行,客户端连接时提示地址不可达,排查了半天才发现是安全组的问题。

第二,系统的文件句柄数限制。Milvus 在运行过程中会打开大量文件,如果系统的ulimit设置太低,可能会报too many open files。建议提前调高:

ulimit -n 65535

第三,Milvus 需要能访问外网拉取镜像。如果你用的是内网服务器或者镜像源受限,建议提前把镜像 pull 下来再导入,否则docker compose up会在拉镜像阶段卡很久。

3.3 Docker 方式的加速小技巧

我自己测试下来,镜像拉取是最耗时的步骤。milvusdb/milvus 这个镜像体积不小,一般在几百 MB 到 1GB 之间。如果网络状况不理想,可以配置 Docker 镜像加速器来提速。

另外,很多人在 Mac 上遇到的问题不是下载慢,而是磁盘空间不足。Milvus 的镜像加上 etcd、MinIO,差不多要占用 3~4GB 空间。如果你 Docker 的 disk image 已经很大了,建议先清理无效镜像再开始,免得中途失败。

4. 本地加载 Milvus 与常见报错:从 URI 到 pymilvus 的真实排查记录

我在项目里遇到过一种让人抓狂的用法:用milvus_uri: str = "./data/milvus.db"这种形式去连接 Milvus。这个写法其实是 Milvus Lite(嵌入式版本),不是完整的服务端模式。

Milvus Lite 是官方推出的一个嵌入式版本,它把整个 Milvus 打包成了一个本地文件,用./data/milvus.db这种路径当作连接参数。这个适合在本地做开发调试、单元测试,不需要 Docker,也不需要启动任何服务。但在使用它的时候,有几个坑特别值得说。

4.1 提示 milvus 相关报错的三种典型原因

第一种:连接模式混用。你写了MilvusClient(uri="./data/milvus.db"),但本地其实已经用 Docker 启动了一个 Milvus 服务,这时两者是冲突的。Milvus Lite 会尝试去读取或创建本地文件,如果文件和目录权限不对,就会报错。解决办法也很简单:要么停掉 Docker 服务,要么统一用uri="http://localhost:19530"走服务端模式。

第二种:缺少依赖。Milvus Lite 需要单独的依赖包支持。如果你只是pip install pymilvus,默认可能没装 Milvus Lite 的本地后端,运行时会提示找不到本地实现的错误。这时候需要安装:

pip install milvus-lite

第三种:数据目录不存在。./data/milvus.db要求./data目录真实存在,否则 Milvus Lite 无法创建底层存储文件。我在一台完全干净的环境上跑,忘记先建data目录,直接报directory does not exist。这个处理很简单,提前创建目录,或者干脆在代码里用os.makedirs保证目录存在。

4.2 本地模式和服务端模式怎么选

如果让我给一个判断标准:开发调试阶段,用 Milvus Lite 的本地模式最省事;一旦涉及到多进程、分布式部署、性能验证,马上切换到服务端模式。

为什么这么说?因为 Milvus Lite 的性能压测结果和真实的服务端有明显差距,它更多是给开发者在写代码阶段的验证用的。你的逻辑可以先用本地模式跑通,构造好 Collection、插入数据、确认查询结果符合预期,再改一行配置切到服务端跑正式测试。

我自己踩过的一个大坑是:在本地模式下测试的数据量只有几万条,查询响应是毫秒级;上线后把同样的代码切到 Docker 版 Milvus,因为索引类型和参数没调优,查询直接退化成了暴搜,延迟翻了几十倍。后来重新设置了 HNSW 的M和efConstruction参数才恢复正常。

5. 核心概念与数据模型:Collection、向量与标量字段的组合

Milvus 的概念体系和传统数据库有不少对应关系,新手第一次接触时容易晕。这里我用一版最通俗的对照表来解释。

Milvus 概念类似 MySQL 概念说明
Collection表一组向量的集合
Partition分区(按时间等)物理分区,用于数据隔离
Field字段包含主键、向量字段、标量字段
Schema表结构定义 Collection 有哪些字段
Entity行记录一条数据,包含向量和标量
Index索引加速向量检索的结构

理解了这个对应关系,后面写代码就顺了。举个实际的例子:我要做一个商品图片检索库,每个商品有商品 ID(主键)、图片向量(512 维)、商品类别(标量)、上架时间(标量)。在 Milvus 中,我定义一个 Collection 叫product_image,它包含这四个字段。

这里要注意一个关键区别:milvus 中主键字段必须提前指定,向量字段也必须在建 Collection 前声明维度。这和 MySQL 的“先建表再随意改字段”的习惯很不一样,一旦 Collection 创建完成,字段结构就不能再改了。我一开始不太适应,后来养成了在业务设计阶段就把 schema 定清楚的习惯。

5.1 用 pymilvus 定义 Schema 的正确姿势

下面是一段实际可运行的代码示例,用于创建一个包含向量字段和标量字段的 Collection:

from pymilvus import ( MilvusClient, FieldSchema, CollectionSchema, DataType ) client = MilvusClient(uri="./data/milvus.db") schema = CollectionSchema([ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True), FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=64), FieldSchema(name="image_vector", dtype=DataType.FLOAT_VECTOR, dim=512), ], description="product image search") client.create_collection( collection_name="product_image", schema=schema )

这段代码做了一件事:用本地模式连接 Milvus Lite,创建一个product_image的 Collection。主键是id,一个字符串字段是category,向量字段是 512 维的image_vector。

如果你用的是服务端模式,只需要改一行:

client = MilvusClient(uri="http://localhost:19530")

5.2 向量维度与相似度度量的选择:余弦值背后的门道

搜索引擎热词里有一个词是“milvus余弦值”,这其实是说 Milvus 里计算向量相似度的度量方式。

Milvus 支持三种主要的相似度度量:

  • COSINE(余弦相似度):衡量两个向量在方向上的相似程度,不考虑长度差异,适合文本、图片这种经过归一化处理的向量。
  • L2(欧氏距离):衡量向量在空间中的绝对距离,数值越小越相似,适合本身分布已经稳定的向量。
  • IP(内积):相当于未归一化的余弦相似度,在推荐系统中比较常用。

选错度量方式对检索效果的影响非常大。我见过有人做文本检索,直接用了 L2,结果模型的输出向量长度差异对检索排序产生了干扰,召回结果肉眼可见地变差。正常来说,文本模型产生的向量在经过 L2 归一化后,用 COSINE 或 IP 的效果是一致的。

设置方式如下:

client.create_collection( collection_name="product_image", schema=schema, # 索引参数中的 metric_type 指定距离度量 )

不过要说明的是,在最新的 pymilvus 2.4.x 版本中,创建 Collection 后还需要单独建索引,度量类型在索引参数里指定。完整代码我会在后面的实操小节里展示。

6. 实用演示:从插入数据到相似度检索的完整流程

概念说完了,现在进入最实用的环节。我用 pymilvus 演示一个完整的“插入向量—创建索引—相似度检索—混合过滤”的流程。这段代码可以直接复制去跑,你只需要一个 Python 3.8+ 的环境。

6.1 插入数据与构建索引

先创建 Collection,然后插入几条测试数据:

import random from pymilvus import MilvusClient client = MilvusClient(uri="./data/milvus.db") # 如果之前已创建,先清理环境 client.drop_collection(collection_name="demo_collection") client.create_collection( collection_name="demo_collection", dimension=8, primary_field_name="id", vector_field_name="vector", metric_type="COSINE", auto_id=False ) # 构造 100 条 8 维的测试向量 data = [ {"id": i, "vector": [random.random() for _ in range(8)], "category": f"cat_{i % 10}"} for i in range(100) ] client.insert( collection_name="demo_collection", data=data )

这里我用了create_collection的简略版接口,它支持只指定 dimension,不用手写 FieldSchema。这对快速验证来说非常方便。

插入完成后,必须要创建索引。Milvus 中索引参数决定了检索的性能和召回率:

client.create_index( collection_name="demo_collection", index_params={ "index_type": "HNSW", # 最常用的 ANNS 索引 "metric_type": "COSINE", "params": {"M": 16, "efConstruction": 200} } )

M是每个节点的最大连接数,efConstruction是构建索引时动态候选列表的大小。这两个值越大,索引质量越高,但构建速度越慢、内存占用越大。小数据集上可以设置 16 和 200,百万级以上可以考虑 M=32。

6.2 查询与混合过滤:带条件的向量检索

数据有了,索引建好了,现在来做查询。假设我要从 100 条数据里找与向量[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]最相似的 5 条:

query_vector = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8] results = client.search( collection_name="demo_collection", data=[query_vector], limit=5, output_fields=["id", "category"] ) print(results)

每个结果里会带id、category和相似度分数。因为用的是 COSINE 度量,分数越接近 1 说明越相似。

接下来是实际业务里更常见的场景:我只想搜索category == "cat_3"这个类别的商品,而不是全库范围搜索。可以用filter参数:

results = client.search( collection_name="demo_collection", data=[query_vector], limit=5, filter="category == 'cat_3'", output_fields=["id", "category"] )

这就是 Milvus 的混合查询能力——标量过滤先圈定候选集,再在这个候选集上做向量检索。注意,过滤条件写法和 SQL 有点像,但语法细节略有不同,相等用==,字符串要用单引号包裹。

6.3 余弦相似度分数怎么看

用 COSINE 度量时,分数字段distance的值范围是 [-1, 1]。越接近 1,代表方向越一致。如果向量都是正数(比如文本模型输出经过 ReLU 之后的结果),分数一般落在 [0, 1] 区间。

我在做结果展示时,喜欢顺手把分数乘 100 再取两位小数,这样在界面上展示“相似度 98%”比直接展示“0.981234”直觉得多。这个小习惯可以提升产品体验。

7. 常见问题与实战避坑速查表

我把日常群里问的最多的问题整理成了一张表,方便你遇到问题时直接对照处理。

问题现象可能原因解决方法
docker compose up后容器反复重启Docker 分配给 VM 的内存不足Docker Desktop 中调高内存到 4GB 以上
连接 19530 端口失败防火墙/安全组未放行放行 19530、9091 端口
pip install pymilvus后本地模式不可用缺少 milvus-lite 包执行pip install milvus-lite
./data/milvus.db路径报目录不存在目录未提前创建手动创建或代码里os.makedirs
Collection 已存在报错重复创建先drop_collection再创建
查询结果为空但数据明明已插入插入后未 flush 或未建索引插入后调用flush并创建索引
检索结果顺序不符合预期度量类型与向量不匹配根据模型特性选 COSINE 或 L2
大批量插入速度慢未使用批量插入接口使用client.insert一次传入多条批量数据
HNSW 检索召回率下降efSearch参数太小查询时传入search_params={"ef": 64}以上

这里重点解释前两个问题。

Docker 内存配额导致的报错,业界叫“OOM Killer 被杀”,但容器表面上不会直接退出,而是内部进程反复崩溃重启。用docker logs <容器名>能看到大量memory allocation failed的日志。解决方案除了调内存,还可以在 Docker Desktop 的设置里关掉 unused images,释放点空间。

连接超时的问题,很多情况不是 Milvus 本身的问题,而是你用的网络环境。比如在 Mac 上,Docker 容器用的是虚拟网络,宿主机localhost:19530可以访问;但如果你是在另一台电脑上访问 Docker 所在机器的 Milvus,一定要把uri改成机器的真实 IP 而不是localhost。这个细节我见过不下五次有人踩坑。

8. 个人实操心得与调优建议

文章写到这,最后分享几条我从项目里沉淀下来的心得,不一定都在官方文档里,但对实际开发很有用。

第一条是关于异步数据写入。Milvus 插入数据默认走的是异步链路,也就是说insert返回成功只代表数据进入了消息队列,不一定立刻可查。如果你在插入后马上查询,可能查不到刚插入的数据。保险做法是插入后调用client.flush()强制落盘,开发调试阶段尤其需要。

第二条是索引的选择策略。数据量小于 100 万行,直接用 HNSW 最简单,性能也够好。超过 100 万行,考虑 IVF_FLAT 或 IVF_PQ,其中 IVF_PQ 能显著压缩内存占用,但召回率会打折。如果对延迟极度敏感且数据量千万级,可以考虑 SCANN 或 GPU 索引,不过部署成本会上升不少。

第三条,及时清理不用的 Collection。Milvus 里如果你创建了一堆临时集合又不清理,元数据会越积越多,控制台看起来也很混乱。我习惯在脚本里先drop_collection再重建,确保幂等。

第四条,关于数据模型设计。向量字段的维度不能改,所以模型升级后向量维度变了,只能新建 Collection 重新导入数据。我建议在业务设计阶段就把模型的向量版本写入 Collection 描述,避免后面混淆。

结语

从 Docker 安装到 Milvus Lite 的本地模式,从 Collection 数据结构到 HNSW 索引调参,这一套流程走下来,你对 Milvus 应该有个比较完整的掌握了。Milvus 的学习曲线不算陡,但要真正用好它,关键在于理解向量检索的底层逻辑和参数调优的实际影响。

如果后面有时间,我会再写一篇关于 Milvus 与 RAG 结合实战的文章,讲讲如何把文档切分、向量化、检索、拼装 Prompt 这整条链路串起来。到时咱们接着聊。

提示:文中所有代码基于 pymilvus 2.4.x 版本和 Milvus 2.4.x 编写,不同版本 API 可能略有差异,遇到报错优先查对应版本的官方文档。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询