☰
向量数据库选型:Chroma、Milvus、pgvector、ES对比与避坑
2026/10/12 21:23:55 网站建设 项目流程

最近几个月,我这边收到最多的技术问题之一就是“我要搭一个知识库,或者做语义搜索,向量数据库到底选哪个?”也难怪,大家一搜资料,满屏都是Chroma、Milvus、pgvector、Elasticsearch这几个名字,每一个都有教程,每一个看起来都靠谱,结果反而是越看越没底。我自己在过去一年里,把这几样东西在不同项目里轮着用了一遍,从本地小脚本到线上服务都碰过,所以这篇不打算单纯翻译官方文档,而是直接聊选型判断、核心原理和落地时的坑。如果你正在做RAG、知识库、相似文本/图片检索,或者只是帮团队做技术预研,这篇文章应该能帮你省下不少试错时间。

先说一个可能反直觉的结论:这四个方案并不是同一类东西,硬比性能没有意义。选型之前,先想清楚你要做的是“一个用完就扔的原型”,还是“一台要长期跑在机房里的服务”。下面我会把这四个方案的原理、实操和避坑经验都完整过一遍。

1. 先搞清楚这四样东西的本质区别

1.1 一句话定位这四套方案

我经常用一句话给团队新人快速建立概念:Chroma是嵌入式向量库,Milvus是独立的分布式向量数据库,pgvector是PostgreSQL的向量插件,Elasticsearch是带向量检索能力的搜索引擎。这几者的本质差异决定了它们的部署方式、扩缩容能力和运维成本。Chroma像你电脑里的SQLite,HNSW索引和检索逻辑都嵌在你的应用进程里;Milvus则是一个完整的数据库系统,有自己的集群角色、元数据服务和存储层;pgvector不引入新服务,但你需要有一个能承担向量检索压力的PostgreSQL实例;Elasticsearch则是在倒排索引之外,额外挂上了一棵向量索引树。

如果这四样东西放到一个表里看,会更直观:

方案本质一句话定位典型部署形态
Chroma嵌入式向量库本地优先,开发最方便Python进程内/单机文件
Milvus分布式向量数据库海量向量、高并发场景的专业选手独立集群,支持K8s
pgvectorPostgreSQL扩展业务和向量数据放一起随PostgreSQL一起部署
Elasticsearch搜索引擎 + 向量在全文检索里加入语义召回独立ES集群

这个差异不是嘴上说说,它直接决定了你后面所有操作路径。比如你选了Milvus,就要面对etcd、MinIO、查询节点、索引节点这些基础设施,甚至有可能会问“为什么我的服务角色没起来”。而选了Chroma,你只需要一个Python环境,pip install之后就能跑。没有哪个方案是绝对正确的,只有哪个更适合你当前的约束条件。

1.2 选型不是比性能,而是比场景

见过很多人在群里争论“谁比谁快”,其实脱离了场景谈性能基本没有意义。我一般会按这几个方向来引导需求。

如果你的目标是三天内做一个本地知识库原型,或者在LangChain里快速验证效果,Chroma是最优先的选择。它开发成本极低,文档也全,你不需要为它单独部署任何服务。如果你们的业务数据本身就存在PostgreSQL里,向量只是文章、商品、用户文档的一个附加属性,那pgvector是天然的答案,因为你能用SQL直接关联业务字段,避免在两套系统之间做数据同步。如果你们的搜索团队已经维护着一套Elasticsearch,现在只是想在原有搜索能力上增加语义召回,那在ES上扩展dense_vector和kNN反而是成本最低的路径。而一旦你的数据规模预计到千万级以上,或者查询QPS需要明显超出单机能力,这时候才是Milvus真正发挥价值的地方。

2. 逐个拆解:四个方案的核心逻辑

2.1 Chroma:本地优先,开发者的“瑞士军刀”

Chroma是我做原型时用得最顺手的方案。它把“集合Collection”作为核心概念,一个Collection里有文档、向量、元数据和ID,用户可以按ID或元数据做过滤。底层索引用的是HNSW,默认距离度量是余弦相似度。因为它是嵌入式设计,数据默认持久化到本地目录,所以非常适合个人知识库、教学示例和内部工具。

LangChain和LlamaIndex里对Chroma的封装非常完善,几十行代码就能把文档切片、embedding、写入、查询串起来。很多人误以为Chroma不能上生产,其实它在数据量不太大、单机可承载的场景里完全能跑。只不过它没有内置分布式能力,如果未来数据规模上来了,或者需要高可用,就得考虑迁移。我见过一些团队把Chroma直接跑在服务器上提供服务,配合持久化目录,稳定性还不错,但需要自己处理备份和并发控制。

需要注意的点是,Chroma默认的embedding函数会在第一次使用时下载模型文件,离线环境经常在这里卡住。正确的做法是提前在应用里统一生成好向量,只把vector和metadata交给Chroma,这样既绕开模型下载问题,也方便以后切换embedding模型。

2.2 Milvus:为海量向量而生的分布式系统

Milvus是一款真正的分布式向量数据库,它从诞生起就盯着“海量向量、高并发检索”这个场景。它的架构里有协调节点、数据节点、索引节点和查询节点,还会用消息队列和对象存储把数据链路串起来。即便单机模式,也至少需要etcd、MinIO、standalone三个组件配合,通过Docker Compose或者Kubernetes部署。生产环境里,Milvus可以把查询节点和索引节点横向扩展,甚至用GPU加速索引构建和检索。

Milvus支持多种索引类型,比如IVF_FLAT、IVF_PQ、HNSW、DiskANN等。其中DiskANN可以把索引放到磁盘上,适合“索引太大放不进内存”的场景。这些索引各有参数,调起来需要一定经验。如果你的业务场景是图片特征召回、短视频相似推荐、亿级知识库切片搜索,那Milvus是这几个方案里最能扛事的选择。但代价是运维复杂度明显要高于其他三个,组件多,日志分散,一旦集群异常,排查链路也会比较长。

我用Milvus的经验是,本地验证和写demo没问题,真正上线前一定要把监控、告警和备份恢复方案提前想好。否则等集群真出了问题,你拿着几十个Pod日志会有点懵。不过话又说回来,正是因为组件多,它才能在一个更大的数据规模上做到可控。

2.3 pgvector:不离开业务数据库的“安静方案”

pgvector不是另一个数据库,而是PostgreSQL的一个扩展插件。它给数据库增加了一种vector(n)字段类型,然后配套提供索引和距离运算。这是它最大的魅力——你的用户表、文章表、订单表本来就在PostgreSQL里,现在只需要多一列存向量,就可以用普通的SQL做向量最近邻搜索。业务事务和向量写入在同一个事务里提交,不会有跨系统一致性的问题。

pgvector支持L2距离、内积和余弦距离这三种度量,索引方面有IVFFlat和HNSW两种。IVFFlat构建快、索引小,但召回率受训练列表影响;HNSW召回率高、查询快,但占用内存更多。在百万级向量规模下,pgvector完全可以胜任。实际项目中,我经常把文章id、作者、标签这些都放在同一个表里,元数据过滤直接用WHERE子句,根本不需要再维护一套过滤系统,查询非常直观。

不过pgvector也有几个容易踩的坑。一是向量的维度在建表时就固定了,比如vector(384),换embedding模型时只能重建列;二是HNSW索引对内存不客气,如果PostgreSQL的shared_buffers和maintenance_work_mem没调好,建索引过程会变得非常漫长;三是向量检索和OLTP业务共用同一个实例时,要评估好资源隔离,否则一个大查询可能把正常业务拖垮。

2.4 Elasticsearch:从全文搜索到混合检索

Elasticsearch本来不是向量数据库,但它在7.x引入了dense_vector字段类型,8.x又补充了HNSW索引和原生kNN搜索,所以现在可以完全在一个ES集群里做向量检索。更重要的是,ES把关键词召回和向量召回放到了同一个框架里,可以用RRF(Reciprocal Rank Fusion)做混合排序。这个能力在做RAG时非常香,因为很多业务查询本身包含明确术语,比如“退款流程”,这时候BM25能精准命中,而向量召回能补充一些语义相似但关键词不重叠的内容。

选择ES的前提通常是:你们已经有ES在跑,或者团队对ES运维很熟。常见的场景包括站内搜索、日志分析、商品检索升级。加入了向量字段之后,每一个文档都会多出一个固定维度的浮点数组,这意味着索引体积、磁盘占用和内存占用都会上涨。ES的JVM堆内存、分片数、段合并策略都会影响向量检索的表现。

配置向量字段时,要注意dense_vector的index参数是否开启。如果开启,ES会为向量建立HNSW索引;如果不开启,只能用script_score做暴力计算,数据量大时性能会很难看。所以不是加一个向量字段就算完事,索引方式必须提前规划好。

3. 选型拆解:数据、查询、运维、成本四个维度

3.1 数据规模:从“千条”到“亿条”,分水岭在哪

我在选型时不会先问“哪个最快”,而是问“你预计数据量会涨到多少”。这里给一个基于我经验的量级参考,不是绝对精确,但用来做初步判断足够了。

数据量级优先考虑方案原因
10万条以内Chroma开发效率高,单机内存足够,几十毫秒内返回
10万到数百万pgvector / Elasticsearch可以靠单机或少量节点扛住,数据库耦合更容易
千万级以上或QPS很高Milvus需要横向扩展,用独立向量库更稳

这个分水岭不是固定的。如果数据量只有几百万但查询QPS非常高,pgvector依然可能成为瓶颈,因为向量检索要消耗大量CPU和内存。反之,如果数据量到了千万但只是离线跑批任务,对延迟不敏感,ES也能硬扛。但是总体来看,数据量和查询并发是两个最核心的限制条件,先按它们画一个选择范围,再谈细节。

3.2 查询方式:相似度度量、元数据过滤、混合检索

很多人的需求并不是“纯向量搜索”,而是“在某个分类下找相似内容”,这就会牵涉到过滤逻辑。四个方案对元数据过滤的支持差别很大:Chroma能用where做简单的等于/包含过滤,但复杂条件支持有限;pgvector使用原生SQL,过滤能力最灵活;Milvus有一套表达式过滤语法,支持范围、IN、布尔组合;Elasticsearch则用filter context和kNN结合。

这里有一个非常容易被忽略的问题:先过滤后检索,还是先检索后过滤。向量索引和业务字段索引是两套独立的索引结构,如果先按元数据过滤,把候选集缩小之后再进向量索引,HNSW图里可用的入口节点会变少,召回质量可能下降。反过来,如果只召回topK条再做过滤,过滤条件严格时最后可能只剩一两条。我常用的做法是:把召回数量扩大到最终期望数量的5到10倍,再做应用侧过滤或精排。如果你用的系统支持filter和kNN深度融合,要仔细读文档,确认过滤发生在向量搜索的哪个阶段。

3.3 业务耦合与一致性:数据往哪儿放

向量数据不应该是孤立存在的,它总是和业务实体绑定。比如一篇文章有标题、正文、作者、发布时间,还有一个对应的embedding向量。如果这些数据都在PostgreSQL里,pgvector能保证同一个事务里同时写入文章和向量,数据不会出现“文章有了,向量没写”的情况。这对很多业务系统来说是很大的优势,不需要额外的消息队列或补偿任务。

Chroma、Milvus和Elasticsearch都是独立存储,相当于多了一个和业务数据库同步的系统。以Chroma为例,你需要自己在应用层写数据写入逻辑,如果写入一半失败,还要考虑如何重试。Milvus和ES同样需要设计好数据源同步方案,比如通过CDC、消息队列或者定时批处理。不要小看这个问题,很多项目上线后真正的麻烦不是检索性能,而是两边数据对不上。

3.4 运维成本:从半夜救火的角度想

如果团队只有两三个人,没有专职DBA或运维,那每引入一个独立中间件,都是在给未来的自己增加值班负担。Chroma最简单,基本等于一个库,备份就是拷贝目录。pgvector则是把运维压力转交给了你已有的PostgreSQL,问题不大,但要注意向量索引资源占用。ES和Milvus都需要认真对待集群监控、版本升级、数据备份和故障恢复。

我见过不少团队,选型时盯着benchmark里最优的那个,结果上线之后没人看得懂监控,版本升级做了一周,线上业务天天被拖累。所以我会强烈建议:在选型阶段就把“未来一年,谁负责维护它”这个问题放到桌面上。选一个更平滑的方案,哪怕单次查询慢几毫秒,长期来看都更划算。

方案部署复杂度监控门槛备份恢复难度
Chroma极低低低,拷贝文件即可
pgvector低中,依赖PG经验中,用PG备份机制
Elasticsearch高高中,快照恢复可用
Milvus高高高,组件多

4. 实操对照:四个系统从零跑通向量检索

4.1 Chroma:一个Python脚本搞定本地语义搜索

本地搭Chroma非常简单,先安装依赖:

pip install chromadb

下面这段代码创建了一个持久化Collection,写入两段文档,并做了语义查询。注意我指定了PersistentClient的path参数,这样数据不会只存在内存里。

import chromadb from chromadb.utils import embedding_functions client = chromadb.PersistentClient(path="./chroma_data") collection = client.get_or_create_collection( name="knowledge_base", embedding_function=embedding_functions.DefaultEmbeddingFunction() ) collection.add( documents=[ "Chroma 适合快速搭建本地知识库原型", "Milvus 适合大规模向量检索场景", ], metadatas=[ {"category": "database", "level": 1}, {"category": "database", "level": 2}, ], ids=["doc_1", "doc_2"] ) result = collection.query( query_texts=["应该用哪个向量数据库做原型"], n_results=2, where={"category": "database"} ) print(result["documents"])

第一次运行会下载Embedding模型,这个步骤在国内网络环境里很看运气。更稳妥的做法是自行生成向量,然后用add时传embeddings参数,查询时用query_embeddings参数。Chroma本身更像一个向量存储和检索引擎,它不一定需要内置模型。

4.2 pgvector:用SQL做最近邻搜索

pgvector的安装取决于你的PostgreSQL分支,通常可以通过CREATE EXTENSION启用。启用之后,建立一个带向量字段的表:

CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE articles ( id bigserial PRIMARY KEY, content text, embedding vector(384) );

插入数据时,需要把文本先经过Embedding模型转成384维向量,再把数组转成PG兼容的字符串格式插入。比如在Python里你可以用str(vector.tolist())生成类似[0.01,0.02,...]的字符串。插入语句大致是:

INSERT INTO articles (content, embedding) VALUES ('pgvector 让 Postgres 拥有向量检索能力', '[0.01, 0.02, ...]');

给向量字段创建HNSW索引:

CREATE INDEX ON articles USING hnsw (embedding vector_cosine_ops);

查询最近的5条记录:

SELECT id, content, embedding <=> '[0.01, 0.02, ...]' AS distance FROM articles ORDER BY embedding <=> '[0.01, 0.02, ...]' LIMIT 5;

这里<=>是余弦距离,<->是欧氏距离,<#>是负内积,使用前要确认和你的模型匹配。如果召回结果差,可以先用SET hnsw.ef_search = 100;调大查询时的搜索宽度,再观察效果。

4.3 Milvus:Docker Compose 单机部署与 Python 客户端

Milvus单机部署最省事的方式是使用官方Docker Compose文件。安装Docker后,在任意目录里执行:

wget https://github.com/milvus-io/milvus/releases/download/v2.4.15/milvus-standalone-docker-compose.yml -O docker-compose.yml docker compose up -d

Windows环境要特别注意Docker Desktop的资源配置,Milvus的etcd、MinIO和standalone三个容器一起跑,内存占用不小,建议给Docker分配4GB以上内存。启动完成后,用Python连接:

pip install pymilvus
from pymilvus import MilvusClient client = MilvusClient(uri="http://localhost:19530") client.create_collection( collection_name="knowledge", dimension=384, metric_type="COSINE", ) client.insert( collection_name="knowledge", data=[ {"id": 1, "vector": [0.1, 0.2, ...], "text": "知识库切片内容"}, {"id": 2, "vector": [0.3, 0.4, ...], "text": "另一段内容"}, ] ) res = client.search( collection_name="knowledge", data=[[0.1, 0.2, ...]], limit=2, output_fields=["text"], ) print(res)

这里必须提醒一点:pymilvus版本之间的API差异很大,旧文档里的connect()、insert()用法在2.3之后变化明显。你如果照着老教程写,可能会遇到方法不存在或参数报错。建议以官方最新文档为准,并且锁好版本号。

4.4 Elasticsearch:用kNN和混合检索改写传统搜索

ES的向量检索在8.x版本里已经比较成熟,核心是先定义一个dense_vector字段。下面这个Mapping创建了一个384维的向量字段,并开启了HNSW索引:

PUT /my_index { "mappings": { "properties": { "content": {"type": "text"}, "content_embedding": { "type": "dense_vector", "dims": 384, "index": true, "similarity": "cosine" } } } }

写入文档:

PUT /my_index/_doc/1 { "content": "Elasticsearch 的向量检索", "content_embedding": [0.1, 0.2, 0.3, ...] }

使用kNN查询:

GET /my_index/_search { "knn": { "field": "content_embedding", "query_vector": [0.1, 0.2, 0.3, ...], "k": 10, "num_candidates": 100 }, "_source": ["content"] }

你也可以把kNN和match查询组合起来,做真正的混合搜索。num_candidates是控制召回候选池大小的参数,默认值偏保守,如果发现召回不理想,可以逐步加大到200甚至300,但会增加协调节点的开销。ES启动时如果只给了默认1GB堆内存,大概率会在写入向量数据后频繁GC,建议本地测试至少设置2到4GB。

5. 实战中高频踩坑的排查清单

5.1 召回率低?先查索引参数和距离度量

很多人在向量检索效果差的时候,第一反应是换Embedding模型。但很多时候问题出在索引参数和距离度量上。HNSW索引有两个关键参数:M控制每个节点的连接数,efConstruction控制构建时的搜索宽度。M越大,图的连通性越好,但内存也越高;efConstruction越大,索引质量越高,构建时间越长。查询时还有一个ef_search参数,决定搜索时检查多少候选节点。

如果效果不满意,建议先把ef_search调大,观察召回率变化,再决定要不要重建索引。另外,距离度量要选对。Cosine距离在向量未归一化时和L2距离的结果排序可能有很大差别。很多开源Embedding模型输出时本身就是归一化向量,用L2和cosine可能差异不大,但如果你在中间又做了拼接或加权平均,最好显式归一化一次。

5.2 元数据过滤:先过滤还是后过滤

我前面提过过滤和kNN结合的问题,这里说一个真实案例。有个项目需要“在某个分类下推荐相似商品”,商品总共十万条,某个分类只有两百条。当时直接在向量搜索后用分类过滤,结果经常出现剩下不到5条结果。后来改成先按分类过滤,再对这两百条做精确距离计算,速度反而更快,效果也更稳。

如果你的过滤条件筛选出的候选集非常小,那么向量索引反而帮不上忙,直接用暴力距离计算更合适。如果你的候选集很大,比如几十万条,还是需要走向量索引,但要通过提高召回数量来弥补过滤损失。所以设计时不要把“过滤”只当成查询里的一个WHERE条件,它会影响整个检索策略。

5.3 写入慢、查询慢:别急着加机器,先看指标

很多人遇到ES或Milvus写入慢,第一反应是“加节点”。但很多问题出自更基础的地方:磁盘IO、内存、段合并、批量大小。向量数据是浮点数组,写入时比普通文本要复杂,磁盘一旦接近饱和,写入性能会肉眼可见地下降。判断磁盘是否有问题,可以先看系统IO等待指标,再查数据库的刷盘频率和段合并统计。ES可以打开慢查询日志,pgvector可以用EXPLAIN ANALYZE看查询计划,Milvus的监控面板里也有查询延迟和索引命中率。

批量写入时不要一条一条插,尝试每次批量500到1000条,往往能明显提升吞吐。如果查询慢,优先检查是否有大量请求打到暴力计算路径上。比如ES里dense_vector没有开启index: true,或者pgvector建的索引没有被查询计划使用,都会导致性能崩塌。先用EXPLAIN看索引是否生效,再考虑加机器。

5.4 维度冻结和模型更换:一个容易忽略的隐性坑

向量维度这件事,在系统设计初期特别容易被忽略。Chroma一个Collection第一次写入后就会锁定维度;Milvus建Collection时dimension必填;pgvector的列类型自带维度;ES的mapping里也写死了dims。这意味着你后期换Embedding模型时,必须新建Collection或表,再重新写入全部向量数据。

我的习惯是在集合命名的阶段就把模型版本带进去,比如wiki_2024_minilm_v2。这样切换模型时,可以并行重建新集合,对外提供只读查询,等数据追平后再切流量。这个习惯能大幅降低线上切换的难度,比临时改Schema优雅得多。

6. 我现在的选择习惯和可复用经验

6.1 我的默认选择顺序

如果现在有人问我这四个方案怎么选,我内心的排序大概是:数据量100万以内、技术栈里已经有PostgreSQL的,直接选pgvector,因为它和业务数据的配合太好了;如果只是快速做原型,Chroma是最舒服的起点;如果公司已经有ES集群,并且要兼顾关键词搜索和语义搜索,ES的混合检索是值得投入的方向;只有当数据规模大概率冲上千万、或者需要独立的高并发向量服务时,我才会一开始就上Milvus。

这个顺序不是一成不变的。我见过一个知识库项目,数据量只有几十万,但因为公司对这个知识库的QPS预期很高,最终选了Milvus,因为ES集群资源不够,pgvector也不好单独扩容。选型最终还是看约束条件。

6.2 一个真正有用的验证方法

我自己的习惯是,在实际选型前不只看文档,而是找一批最有代表性的真实数据,大概1000到5000条就够,然后分别用候选方案跑一遍。测试用的查询语句也要真实,比如做客服知识库,就把客户真正会问的问题拿去搜,看前几名结果能不能直接用。再记录一下单次查询耗时和写入耗时。

这个验证过程比看任何benchmark都有用,因为你会很快发现:有的方案在测试数据上“什么都好”,但一加入元数据过滤就性能骤降;有的方案安装部署特别顺利,但写入一批真实文本后内存涨得飞快。我踩过几次坑之后,现在几乎都会先跑这么一轮“最小可行验证”,再拍板。毕竟选型这种事,改起来比一开始多花十分钟的成本要高得多。

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

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

立即咨询