向量数据库生产选型实测:Milvus与pgvector的全面对比

发布时间:2026/9/14 9:11:30
向量数据库生产选型实测:Milvus与pgvector的全面对比 向量数据库这两年已经不是什么新鲜词汇了尤其是RAG应用普及以后身边动不动就是“我要搞个向量库”。但落到生产环境真正纠结的往往不是“要不要用”而是“到底选哪家”。我在过去几个月里先后把 Milvus 和 pgvector 都拉到了同一批真实业务数据集上做了一轮完整的测评从部署、索引、查询延迟、并发、运维成本到灾难恢复都过了一遍。这篇就把整个过程和结论完整摊开包括中间踩过的坑和调参记录希望能给正在做生产选型判断的人一个相对踏实的参考。先说结论这两个东西压根不在同一个赛道上。Milvus 是专业向量数据库pgvector 只是 PostgreSQL 的一个扩展插件。它们各有各的甜点区也各有各的死角。如果只是为了给一个几千条数据的小应用配上“语义搜索”能力直接上 Milvus 属于杀鸡用牛刀运维成本分分钟劝退但如果你要处理千万级以上的向量数据还要支撑高并发检索pgvector 大概率会先把你自己的数据库拖垮。很多人选型翻车就是没认清这一层事实。1. 内容整体设计与思路拆解1.1 向量数据库到底在解决什么问题在开始做对比之前先搞清楚一个基础问题为什么传统数据库搞不定向量检索。常规的 B-Tree 索引是按“精确匹配”或“范围查询”设计的它能回答的是“userId 123 的数据有哪些”但没法回答“哪 10 条文本跟这段话语义最接近”。后者的底层逻辑是计算向量之间的余弦相似度或欧氏距离这是一种高维空间里的近似最近邻搜索ANN跟传统索引优化的方向完全不一样。所以向量数据库的核心能力就是在海量高维向量里快速找回“最相似”的那一批。这中间涉及三个层面的关键技术索引结构IVF、HNSW、DiskANN 等、距离计算方式L2、内积、余弦、以及针对大规模数据的分片和并行检索策略。Milvus 和 pgvector 在这三个层面的实现深度差异巨大这直接决定了二者在生产环境中的表现天花板。为了把这次测评做得更有说服力我准备了一套统一的测试数据从业务日志里抽出的 50 万条中文文本经过 embedding 模型处理成 768 维向量总共约 15GB 的向量数据。测试环境是两台配置相同的物理机一台部署 Milvus一台部署 PostgreSQL pgvector避免硬件差异带来的不公。1.2 选型测评的框架不能只看查询速度很多人做数据库选型上来就只盯着“查询要用多少毫秒”这一件事。但真实生产环境里查询延迟只是其中一个维度。我在这次测评里重点跟踪了六项指标写入吞吐灌入 50 万条向量花了多久写入过程中有没有阻塞。查询延迟在不同并发压力下top-K 检索的平均延迟和 P99 延迟。召回率用同一批 query 跑出结果跟暴力全量扫描对比看看近似检索到底“近似”了多少。资源开销CPU、内存、磁盘的占用情况尤其是空闲和高峰两个状态。运维复杂度从部署到升级到备份恢复中间要手动处理哪些环节。生态整合度跟现有业务系统、监控体系、数据管道的打通方便程度。这里要特别强调召回率这个指标。很多评测文章只敢报延迟数据闭口不提召回率因为召回率一旦降下来查询再快也没有意义。在实际测试中你会发现索引参数的设置直接决定了召回率和性能之间的平衡这也是后面调参环节的重头戏。2. 核心细节解析Milvus 的架构与实测表现2.1 Milvus 的架构分库分表思维在向量领域的延伸Milvus 的架构设计理念简单来说就是把搜索引擎的那套分布式思路搬到了向量检索上。它的核心组件包括接入层Proxy、协调服务Coordinator、工作节点Worker Node以及存储层etcd MinIO 元数据。数据写入时先进入消息队列然后由工作节点构建索引最终落到对象存储里。这套架构带来的最大好处是存储与计算分离。查询请求打到 ProxyProxy 根据元数据把请求路由到对应的 segment 上工作节点并行计算后再合并结果。这个模型跟 ClickHouse 的分布式查询思路很像天然适合横向扩展。你数据量上来了加 worker 节点就行查询并发压不住了加 proxy 节点就行。但这种架构也有代价。它对部署的完整度要求很高etcd、MinIO、消息队列一个都不能少。虽然 Milvus 官方提供了 docker-compose 和 Helm Chart但真要把它跑成一套高可用的生产集群组件之间的协调、监控、日志收集、升级策略每一样都需要专门的投入。这就是很多人说的“Milvus 运维重”的真正来由。2.2 单机部署实践从 docker-compose 到连接验证我这次测试用的是 Milvus 最新稳定版本通过 Docker Compose 部署单机模式。官方仓库里的 standalone 配置文件已经整合了 etcd 和 MinIO一条命令就能拉起来对测试环境来说足够友好。# 拉取部署文件 wget https://github.com/milvus-io/milvus/releases/download/v2.4.1/milvus-standalone-docker-compose.yml -O docker-compose.yml # 启动服务 docker-compose up -d # 确认容器状态 docker-compose ps启动完成后依次检查 etcd、MinIO、Milvus 三个容器的健康状态。如果是在 Linux 服务器上跑大概率会遇到端口占用或防火墙拦截的问题我这边排查下来基本是 19530Milvus 端口和 9091MinIO 端口被占。清理掉冲突后通过 Python SDK 连接验证from pymilvus import connections, utility # 连接到 Milvus 服务 connections.connect(aliasdefault, host127.0.0.1, port19530) # 检查服务状态 print(utility.get_server_version())这里有个很关键但文档里写得比较隐晦的点Milvus 2.x 之后连接对象本身不再暴露给用户直接操作所有操作都通过Collection对象来发起。初次从 1.x 迁移过来的同学很容易在这上面卡住。2.3 索引与检索实测HNSW 的参数调优记录Milvus 支持的索引类型比较多包括 FLAT、IVF_FLAT、IVF_SQ8、HNSW、DISKANN 等。在生产场景下绝大多数团队最终会落在 HNSW 或 IVF_SQ8 之间做选择。HNSW 的召回率高、查询延迟低但内存开销较大IVF_SQ8 用量化压缩换内存索引体积小但召回率会受量化损失影响。我这次的 50 万条 768 维向量用的就是 HNSW。from pymilvus import FieldSchema, CollectionSchema, DataType, Collection # 定义集合并创建索引 fields [ FieldSchema(nameid, dtypeDataType.INT64, is_primaryTrue), FieldSchema(nameembedding, dtypeDataType.FLOAT_VECTOR, dim768) ] schema CollectionSchema(fields, description测评集合) collection Collection(namebenchmark, schemaschema) # HNSW 参数设置 index_params { index_type: HNSW, metric_type: COSINE, params: {M: 16, efConstruction: 200} } collection.create_index(field_nameembedding, index_paramsindex_params)如果你没有实际调过 HNSW可能对M和efConstruction这两个参数没什么概念。简单打个比方M决定每个节点最多连多少根“线”线越多图越密搜索路径越短但内存占用和构建时间也越高efConstruction控制构建索引时的搜索宽度数值越大构建越慢但图质量越好。实测下来M16、efConstruction200是一个兼顾构建速度和召回率的配置继续往上加参收益就很有限了。灌数据时要把collection.flush()的时间也算进去因为 flush 才是真正把数据落盘并触发索引构建的环节。50 万条向量灌完大约用了 40 分钟数据文件约 15GB构建完 HNSW 索引后整体占用约 24GB 内存。这个内存开销已经能说明问题了在 Milvus 上跑 HNSW内存预算得按“原始数据 1.5 倍以上”来捏。2.4 Milvus 的查询性能和缺陷暴露完成索引构建后我跑了三轮查询测试单并发、50 并发、200 并发。在单并发下top-10 查询平均延迟在 8ms 左右效果非常理想50 并发时延迟升到 28-35ms依然可接受但到 200 并发时P99 延迟直接飙到 180ms 以上而且查询进程的 CPU 占用打满说明瓶颈开始出现在计算资源层面。Milvus 的第二个问题是小数据集下的资源浪费。它的调度单位是 segment哪怕你的集合只有几百条数据查询也依然要走完整的“Proxy 路由到工作节点再从 MinIO 加载数据”的链路。这个架构设计是为大规模数据准备的在小数据量下会显得反应迟钝而且资源占用极其不划算。不过说实话Milvus 在召回率上的表现确实稳。我在同样的数据上先用暴力扫描FLAT跑了一版基准结果再拿 HNSW 的检索结果对比top-10 的召回率能稳定保持在 95% 以上。这个数字在真实场景里足够用了。3. 核心细节解析pgvector 的务实主义路线3.1 pgvector 的工作机制与底层逻辑pgvector 跟 Milvus 完全是两种物种。它不是独立数据库而是 PostgreSQL 的一个扩展本质上是往 PG 内核里加了一种新的数据类型vector和对应的索引方法。这意味着它不用引入新的服务、新的运维组件、新的数据管道只要在现有 PG 实例上执行一条CREATE EXTENSION vector;就能开始用。pgvector 目前提供两种索引IVFFlat 和 HNSW。IVFFlat 需要先对数据做聚类然后按聚类中心划分到不同的列表里查询时只搜索最近的几个列表。HNSW 的逻辑跟 Milvus 里的 HNSW 基本同源之所以说是“基本”是因为它的实现是针对 PG 的存储引擎做了适配参数命名稍有不同。这种“依附现有系统”的设计让 pgvector 的吸引力主要落在架构简洁和运维成本低上。你不需要额外维护一套高可用的分布式集群也不用关心组件版本兼容性只要 PG 活着向量检索能力就跟着活着。3.2 pgvector 的部署比想象中简单但也没那么简单pgvector 的安装过程在绝大多数 Linux 发行版上都很直接。我用的是 PostgreSQL 16# 安装扩展以 Ubuntu/Debian 为例 apt install postgresql-16-pgvector # 进入数据库启用扩展 psql -U postgres -d mydb CREATE EXTENSION IF NOT EXISTS vector;到这里扩展已经启用接下来建一张带向量字段的表。pgvector 支持定义向量维度但必须在建表时显式指定CREATE TABLE documents ( id BIGSERIAL PRIMARY KEY, content TEXT, embedding VECTOR(768) ); -- 创建 HNSW 索引 CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops) WITH (m 16, ef_construction 200);注意vector_cosine_ops这里指定了操作符类别它决定了距离计算方式。可选的有三个vector_l2_ops欧氏距离、vector_ip_ops内积、vector_cosine_ops余弦相似度。选错操作符类别索引就白建了查询时根本不会走索引。用文本相似度检索场景的默认选项是vector_cosine_ops。如果业务偏重推荐系统的 embedding 匹配vector_ip_ops可能更贴合内积计算的语义。这点在官方 README 里没有大声强调但实践中非常重要。3.3 pgvector 生产使用混合查询的优势被低估了pgvector 一个非常实用但容易被测评文章忽略的特长是 SQL 生态带来的混合查询能力。向量相似度只是过滤条件之一在同一个查询里可以把向量检索和结构化条件自由组合SELECT id, content, 1 - (embedding $1) AS similarity FROM documents WHERE category technology AND created_at NOW() - INTERVAL 30 days ORDER BY embedding $1 LIMIT 20;这个能力在业务系统里非常好用。比如一个知识库检索场景你可能既想要语义相关的内容又要求只返回“未删除、指定部门、最近一个月”的记录。用 pgvector 一条 SQL 就完成了完全不需要在应用层做二次过滤。Milvus 也支持标量过滤但毕竟要额外维护一套过滤逻辑而且标量过滤的性能在复杂条件下会明显下降。当然pgvector 不等于没有代价。它的短板非常清晰大数据量下性能下滑严重索引构建时间长写入吞吐受制于 PG 本身的限制。我同样灌入 50 万条 768 维向量在 pgvector 上构建 HNSW 索引花了一个半小时是 Milvus 的 2 倍多。查询并发上去之后内存和 CPU 消耗也在快速上涨因为 PG 的每个后端进程都要独立加载索引相关数据。3.4 pgvector 的性能实测数据我拿同一份测试数据跑了同样的查询测试。单并发下 top-10 查询平均延迟 12ms比 Milvus 略高50 并发时延迟升到 55ms已经开始有压力200 并发时 P99 延迟到了 500ms 以上几乎是 Milvus 的 3 倍。这个差距在多维向量上体现得尤为明显。768 维已经是 pgvector 的舒适区上限边缘如果换到 1536 维OpenAI embedding 的默认维度甚至更高性能下降会更厉害。所以在我们的测试里pgvector 更适合把“能用”作为目标不适合去追“极致性能”。4. 生产选型决策到底怎么选才靠谱4.1 不同场景下的选型建议我把自己遇到的真实项目需求分成了四类并给出了对应的选型判断。这不是什么官方推荐而是我基于这次测评以及之前几个项目的实操经验得出的结论。业务场景数据规模推荐方案核心理由小型 RAG / 个人知识库万级以下pgvector简单直接无需额外服务中型企业应用已有 PG 依赖百万级以下pgvector减少架构复杂度混合查询方便大规模搜索 / 推荐系统千万级以上Milvus分布式扩展能力和检索性能优势明显高并发低延迟在线服务任何规模Milvus查询性能更稳定P99 更可控这里需要解释一下“数据规模”和“并发”这两个变量的关系。如果你的业务上限就是几万条数据一天也就几百次查询请求选 Milvus 除了给自己找麻烦没有任何收益。它要维护的那套组件随便出点问题就够你喝一壶的。反过来如果你的用户在千万级、查询请求每秒上百次pgvector 的 PG 后端进程模型会先撞到内存瓶颈到时候再迁移就是一场灾难。4.2 成本模型显性成本和隐性成本都要算很多选型报告谈到成本只盯着服务器单价和存储费用但真正的成本大头往往藏在别处。我把自己在这两个方案上的花销做了个简单对照pgvector 的成本结构显性成本基本为零它就是 PG 的一个插件。隐性成本查询性能不足时你可能要堆更高配置的 PG 实例索引重构和 vacuum 需要额外监控。人力成本PG 运维团队基本不用学新东西上手成本极低。Milvus 的成本结构显性成本至少三套组件要跑etcd、MinIO、Milvus 本身资源占用明显高于单个 PG 实例。隐性成本组件升级、监控告警、数据备份策略每一项都需要额外投入。人力成本需要一个懂分布式系统的人来维护这不是普通 DBA 能直接接手的。我见过很多技术团队选 Milvus纯粹是因为“想用更先进的技术”却没有算过这套架构背后的人员成本和学习曲线。技术选型不是挑最炫的而是挑最匹配团队能力的。4.3 一条务实的迁移路径从 pgvector 起步为 Milvus 留接口如果让我给正在做技术规划的团队一个建议我会说不用急着一步到位上 Milvus。一个更稳妥的做法是初期先把 pgvector 用起来把产品逻辑和数据流程跑通同时在代码层把向量检索的接口抽象出来。等数据规模真正涨上来、性能瓶颈开始出现在眼前时再迁移到 Milvus 也不迟。这个迁移路径的好处在于你在初期用最小的成本验证了业务价值同时保留了技术上探的空间。向量检索这个领域还没到“一家独大”的成熟期各种新方案百花齐放早点绑定某一家反而可能陷入被动。从实现层面来看抽象检索接口的代码量并不大。无非就是定义一个统一的检索方法内部根据配置切换实现pgvector 和 Milvus 各自落在不同的实现类里。5. 实操过程与核心细节实现5.1 用同一套数据跑通两个系统的完整流程为了确保对比的公平性我把两个系统的数据准备流程统一成了一条 pipeline。Embedding 模型用的是 bge-large-zh-v1.5输出维度 768。数据从业务库里抽取后先做文本清洗然后批量向量化最后分别写入两个系统。整个过程跑下来最花时间的不是向量化而是处理 embedding 服务在高并发下的限流。from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-large-zh-v1.5) def embed_texts(texts): embeddings model.encode( texts, batch_size32, normalize_embeddingsTrue ) return embeddings.tolist()这里有个细节normalize_embeddingsTrue必须打开。归一化之后余弦相似度和内积等价后续无论选哪种距离计算方式都能得到一致的结果排序。5.2 Milvus 的批量写入与索引构建实测Milvus 官方 SDK 提供了高效的批量写入接口可以在一次请求里传入一个实体列表。我把 50 万条向量分批写入每批 5000 条from pymilvus import Collection collection Collection(benchmark) batch_size 5000 for i in range(0, total_count, batch_size): batch data[i:i batch_size] collection.insert([ [item[id] for item in batch], [item[embedding] for item in batch] ])写入过程中我观察到 Milvus 的数据段会在后台不断生成和刷盘CPU 和内存的占用随着 segment 的增长而波动。全部写入完成后执行collection.flush()触发索引构建。这个过程用时约 40 分钟索引构建期间查询服务依然可用但查询延迟会明显升高建议在业务低峰期执行。5.3 pgvector 的写入与索引构建实测pgvector 的写入方式就是普通的 SQL INSERT我用COPY命令批量灌数据速度比逐行 INSERT 快一个数量级\COPY documents (id, content, embedding) FROM /path/to/embeddings.csv WITH (FORMAT csv, DELIMITER ,)50 万条数据用 COPY 灌入耗时约 8 分钟比 Milvus 的写入快因为省掉了分布式协调和刷盘的开销。但随后构建 HNSW 索引的耗时长达 90 分钟而且索引构建期间 PG 会持有表锁所有对这个表的写入操作都会被阻塞。这个特性在生产环境非常致命必须在业务维护窗口执行索引构建。5.4 参数调优对比同样的 HNSW不同的调优方向Milvus 和 pgvector 虽然都支持 HNSW但参数的调优空间和最佳实践完全不同。Milvus 的ef参数可以在查询时动态调整给了你“快查”和“精查”的自由切换能力。pgvector 的ef_search虽然也支持设置但作用范围是会话级别灵活性差一些。-- pgvector 设置动态搜索宽度 SET hnsw.ef_search 100;这个参数决定了查询时会在图上扩展多少候选节点。数值越大找到真正最近邻的概率越高但查询耗时也越长。我在测试里发现ef_search从默认的 40 提到 100召回率能从 88% 提升到 97%查询延迟只从 12ms 涨到 18ms这个性价比非常划算。Milvus 在查询参数里也有对应的ef参数search_params { metric_type: COSINE, params: {ef: 100} } results collection.search( dataquery_vectors, anns_fieldembedding, paramsearch_params, limit10 )经验教训是无论用哪个系统都不要直接使用默认参数跑生产。花半小时跑一组参数实验往往能带来 10 个百分点的召回率提升。5.5 监控与告警两个方案各自的注意点生产环境最怕“黑盒运行”所以监控体系必须在系统上线的第一天就搭好。Milvus 需要重点监控的指标包括各组件proxy、index node、query node的 CPU/内存、etcd 的健康状态、MinIO 的存储占用、segment 的数量、查询延迟分位数。官方提供的 Prometheus 监控面板基本覆盖了这些指标但需要自己部署采集器。pgvector 的监控相对简单重点关注 PG 本身的慢查询日志和锁等待事件就够了。一个值得留意的指标是idx_scan与seq_scan的比例如果发现 seq_scan 比例过高说明索引没有走到查询在暴力扫描。用这条 SQL 就能查到SELECT relname, idx_scan, seq_scan FROM pg_stat_user_tables WHERE relname documents;如果seq_scan远大于idx_scan优先检查查询条件里的操作符类别是否与索引的类别匹配。这是我踩过的最常见的坑没有之一。6. 常见问题与排查技巧实录6.1 Milvus 部署和使用的典型问题我这次在用 Milvus 的过程中遇到并解决了下面这几个问题每一个在官方文档里都写得不算直白遇到了容易卡壳问题一Docker Compose 启动后连接一直超时。排查后发现是因为 minio 容器启动慢etcd 一直在重试Milvus 迟迟没有注册成功。解决方法是先等两分钟再连或者把 etcd 和 MinIO 的健康检查配置好再启动 Milvus 容器。问题二删除集合后磁盘空间没有释放。Milvus 的数据最终落在 MinIO 对象存储里删除集合只是逻辑删除物理文件要等后台的垃圾回收任务执行后才真正清理。如果磁盘空间吃紧可以手动触发一次数据清理。问题三查询结果出现重复 ID。这个大概率是因为数据段没有合并。多个 segment 各自返回 top-K合并结果时可能带上重复项。在调用search时通过参数控制每个 segment 的返回数量能缓解这个问题。问题四长时间运行后查询越来越慢。数据段持续增加会导致查询需要扫描的 segment 数量变多。Milvus 提供了compact操作来合并 segment定期执行能明显改善查询性能。collection.compact()这些问题的共同点是Milvus 的架构在单机环境下容易“小马拉大车”很多在集群环境才需要的功能单机版也会同步运行只是用户看不见。知道这些机制排查起问题来就能少走弯路。6.2 pgvector 的典型坑pgvector 踩坑的维度不太一样问题往往出在 PG 和业务侧的配合上。问题一IVFFlat 索引的召回率暴跌。最典型的场景是先建好了 IVFFlat 索引再灌入大量新数据导致聚类中心严重失真。IVFFlat 必须在数据量基本稳定后再建索引索引建成后的大规模数据变更都会影响召回率。问题二写入性能悬崖式下跌。给向量字段建了索引之后每次 INSERT 或 UPDATE 都要同步更新索引。HNSW 索引的更新开销本来就大加上 PG 的 WAL 日志和 vacuum 机制写入吞吐自然会掉落。如果业务对写入有高要求建议把向量写入做成批量异步的。问题三直接跑ORDER BY embedding $1没有走索引。检查后发现是操作符类别和查询条件不匹配。索引用的vector_cosine_ops查询里用的却是embedding - $1欧氏距离的写法PG 优化器直接放弃索引转成了全表扫描。把查询条件改成之后执行计划立刻正常了。问题四PG 日志里频繁出现 deadlock。建了 HNSW 索引之后高并发写入时索引页的锁竞争会比普通表结构更激烈。解决思路有两个降低单事务的写入行数或者把索引构建放在夜间维护窗口。6.3 两套系统的备份与灾难恢复对比这个点很多测评文章都不提但对生产环境至关重要。Milvus 的备份相对复杂。它需要同时备份三样东西etcd 中的元数据、MinIO 中的对象数据、以及数据段的元数据文件。官方推荐的备份工具是 Milvus Backupper支持手动触发备份和恢复但恢复过程需要在一套全新的 Milvus 环境上执行步骤多、时间长。实测恢复 50 万条数据大约花了 25 分钟。pgvector 的备份就简单多了。它跟普通表数据没有任何区别直接用 pg_dump 整个库或者单独导出这张表即可。恢复时先建表、再建索引整个流程在 15 分钟内完成。如果你的业务对 RPO恢复点目标要求不高pgvector 的备份方案着实更省心。7. 写在最后的个人经验整个测评做下来我最大的感受是这两个工具并不存在于同一个竞争维度硬要比个高下意义不大。Milvus 的定位是“专业的向量检索基础设施”它割舍了通用性换来了极高的性能和扩展性pgvector 的定位是“让传统数据库平滑获得向量能力”它牺牲了极致性能换来了无感的接入成本和家族化的生态一致性。如果让我给一个更具体的建议新项目首次上线数据量不确定团队没有专门的运维支撑先选 pgvector 没毛病把业务跑通才是第一要务。但如果你已经明确知道数据规模会快速起来或者查询并发就是高那就直接上 Milvus别在中间地带反复横跳。最后分享一个基于这次实践总结出来的小技巧不管最终选了哪套方案都要把“索引参数调优”和“数据生命周期管理”写进日常运维手册而不是等项目上线后才想起来。向量数据的索引质量和数据新鲜度是影响检索效果的两个最核心变量它们出问题的方式往往是缓慢恶化的不会像服务宕机那样立刻报警但这恰恰是生产事故中最容易忽略的一种。假如后面有时间我还会把 Qdrant 和 Elasticsearch 的向量检索能力都拉进这个对比框架里跑一轮毕竟现在向量数据库这个赛道还远没到格局已定的阶段多掌握几个候选方案后续做架构决策时心里会更有底。