向量数据库选型与实战:Milvus、pgvector、Chroma等8款对比与部署指南
发布时间:2026/9/9 6:52:35 作者:尧图编辑部 阅读量:1,286

最近项目群里好几个人都在问同一个问题团队准备做知识库问答和语义检索向量数据库到底选哪个网上搜一圈Milvus、Qdrant、Weaviate、Chroma、pgvector、OpenSearch、LanceDB、Neo4j光名字就能把人绕晕。更麻烦的是每个项目的宣传口径都差不多都说是高性能、高扩展、云原生。真的上手折腾一圈之后你会发现差异比想象中大得多选错代价还挺高的。这篇文章我就把自己实际部署和使用这几款数据库的经验整理出来包括它们各自适合什么场景、底层原理有什么不同、部署时容易踩哪些坑。重点覆盖大家在群里问得最多的几个点Milvus 怎么装、Attu 到底配哪个版本、Chroma 添加集合后那一堆表是什么关系、pgvector 怎么跟知识库结合。希望能帮你少走弯路看完基本能确定自己的选型方向。1. 先想清楚一个问题你真的需要独立向量数据库吗1.1 十来条向量到底存在哪很多团队一上来就问“用 Milvus 还是 Qdrant”但实际上连业务场景都没说清楚。我见过不少项目知识库里就几千条文本切片Embedding 向量维度也不高结果最先把 Milvus 集群搭起来了三个组件一部署运维成本直接翻倍。这种规模下用 pgvector 甚至直接内存计算都绰绰有余。选型的第一步不是比参数而是先回答几个基础问题向量规模多大是十万级、百万级还是亿级查询延迟要求是多少是毫秒级还是秒级能接受有没有复杂的元数据过滤需求比如要按用户ID、时间范围过滤后再做向量检索团队有没有专门的运维人力这几个问题的答案基本能把选项缩小到两三个。我个人见过最普遍的误区是“为了向量数据库而向量数据库”。如果项目本身已经在用 PostgreSQL而且数据量在百万级以内直接装 pgvector 扩展是最务实的选择不用引入额外的中间件事务、备份、权限管理全都复用现有体系。反过来如果目标是做独立产品、数据量千万级以上而且索引类型、分片策略都要精细控制那还是老老实实上 Milvus 或 Qdrant。1.2 选型前先列一张需求清单我建议在选型之前把需求写成一页清单每个候选数据库都拿这页清单去对照。清单里至少包含这些维度数据规模和增长预期当前多大、一年后大概多大查询模式是纯 KNN 检索还是 Scalar Filter Vector Search一致性要求数据写入后多久可见可用性和容灾要求需不需要多副本、跨机房开发语言和框架语言比如 Java 技术栈要特别注意 SDK 成熟度运维能力和成本能不能接受多组件部署生态整合需求是否要跟 LangChain、LlamaIndex 等框架对接把这张表填完很多纠结就自动消失了。比如团队主要是做 Java 后端LangChain4j 已经引入了那 Milvus 的官方 Java SDK 就比 LanceDB 成熟得多虽然 LanceDB 也有 Java 客户端但生态丰富度不在一个层级。再比如如果只是给内部工具加个语义搜索功能不想专门运维一个集群Chroma 或者 LanceDB 这种嵌入式方案省心太多。2. 八款主流向量数据库逐个拆解2.1 Milvus重量级选手适合海量规模Milvus 是目前开源向量数据库里功能最全、也是架构最复杂的一个。它内部拆了好几个组件负责元数据管理的 etcd、负责存储的对象存储 MinIO、负责日志和消息流处理的 Pulsar 或 Kafka、计算节点 QueryNode 和 IndexNode。这套架构带来的直接好处是存储和计算分离扩展性很强单机到分布式集群是平滑演进的。Milvus 的向量索引类型也比较丰富FLAT、IVF_FLAT、IVF_PQ、HNSW、DISKANN 都有覆盖了从精确检索到超大规模近似的场景。它的 Scalar Filter 能力在 2.3 之后的版本提升明显可以组合多个字段做过滤这一点是真刀真枪和企业级需求对接过的。代价就是运维复杂度。如果你用 Docker Compose 部署至少要拉起三四个容器如果你用 Helm 部署到 Kubernetes组件数量更多。我见过生产环境把 Milvus 跑在 K8s 上的确实稳定但调试问题的时候需要懂 etcd、懂对象存储、懂消息队列对团队要求比较高。如果项目只是几万条数据Milvus 的启动成本和维护成本完全没必要。2.2 QdrantRust 写的高性能选手Qdrant 是我个人比较偏爱的项目。它用 Rust 实现性能很猛单机就可以承载很大的数据量。Qdrant 的 API 设计很符合直觉用起来像在操作一个带特殊索引的文档数据库Payload也就是标量字段可以灵活附加在向量上过滤直接走 JSON 条件嵌套过滤也支持得不错。Qdrant 的索引核心是 HNSW但它做了不少优化支持按 segment 管理数据写入时会建内存索引超过阈值就 flush 到磁盘。这个机制在实际使用中体验很好批量导入速度比 Milvus 更容易控制因为不需要单独消化消息队列积压。部署方面Qdrant 一个二进制文件就能跑起来Docker 部署也简单默认只依赖一个 volume 存储数据。加上 Qdrant 自带 Web UI查看 collection 状态、测试检索结果都挺方便。新版还加了 Shard Replication可以在分布式模式下做多副本。如果你的数据量在百万级到千万级之间而且不想被一堆组件绑架Qdrant 是非常均衡的选择。2.3 Weaviate自带语义层的全家桶Weaviate 的思路和前三者都不太一样它不只是向量存储还内置了向量化模块。你可以直接用 text2vec-openai、text2vec-transformers、multi2vec-clip 这些模块在写入数据的时候自动调用模型生成向量。这个设计对业务团队很友好不用自己在写入链路里拼 embedding 接口。Weaviate 的查询语言是 GraphQL如果你本来就熟悉 GraphQL上手非常快。它把近邻搜索、BM25 全文检索、标量过滤全部融合在一套查询语法里可以实现混合检索。数据模型方面每个对象都要属于一个 ClassClass 里定义向量化模块和属性和传统数据库的表结构思维有区别刚接触的人需要适应一下。Weaviate 的运维复杂度介于 Qdrant 和 Milvus 之间。单机模式用 Docker Compose 可以跑生产环境推荐用 K8s Operator。令我印象比较深的是它的模块化设计非常模块化比如你不想要向量化功能可以只开 core 模块行为就接近纯向量库了。2.4 Chroma轻量开发调试利器Chroma 是这几款里最轻的安装就是 pip install chromadb代码里直接 ChromaClient 开始用。它会默认把数据持久化在一个本地目录底层用 SQLite 存储不需要额外启动服务当然也支持 Client/Server 模式。对最早期的原型验证和个人项目来说没有比它更简单的方案。Chroma 的 Python API 设计得很简洁add、query、update、delete 几个方法就解决了大部分需求返回结果直接是 Python 对象和 LangChain 配合非常顺滑。它的 embedding_function 默认是 all-MiniLM-L6-v2也可以用 OpenAI、HuggingFace 的模型替换。但你要清楚Chroma 的强项不是海量高性能。数据量一大并发一高SQLite 后端的瓶颈就暴露出来了。它没有成熟的分布式方案也没有精细的索引参数控制。所以我的定位很明确原型验证、课程项目、内部小工具可以用 Chroma生产环境核心链路慎用。另外群里有朋友问过用 Chroma 添加集合后为什么 SQLite 里产生了很多表这个我在第四章专门讲。2.5 pgvectorPostgreSQL 的近水楼台pgvector 是 PostgreSQL 的一个扩展它不是独立数据库而是把向量类型和索引能力直接塞进 PostgreSQL。装完扩展后你可以像建普通表一样建一张带 vector 字段的表然后查的时候按向量距离排序。pgvector 支持两种索引IVFFlat 和 HNSW。IVFFlat 需要先有数据训练中心点适合数据基本不删除的场景HNSW 无需训练写入过程中就能实时构建索引查询精度也更高。实际项目里我更推荐 HNSW因为它少一步训练流程而且对增量数据更友好。pgvector 最大的优势是少一个组件。PostgreSQL 本身的备份、权限、高可用工具全部复用开发团队几乎不用学新东西。如果原有系统已经是 PG 体系加一个扩展实在是很香。它适合的数据量级是百万到千万之间再往上专业的独立向量库在索引并发控制、内存利用上会有优势。2.6 OpenSearch检索生态的老玩家OpenSearch 是 Elasticsearch 的一个开源分支它里面内置了 k-NN 插件支持 HNSW、IVF、NMSLIB 等多种近似最近邻搜索算法也能做纯向量暴力精确检索。如果你的技术栈已经重度依赖 ES 生态有现成的索引管理、分词、聚合分析体系那 OpenSearch 加入向量能力是顺理成章的。OpenSearch 的向量写入本质上还是走 Lucene 的索引文件这意味着它的血缘和全文检索高度同源适合做关键词 向量的混合检索场景。比如先通过传统分词把文档召回一批再通过向量把相似文档扩散召回这种操作在 OpenSearch 里可以用一条 DSL 查询实现。不过 OpenSearch 的问题在于它不是纯粹的向量数据库海量向量场景下的表现不如 Milvus/Qdrant 这类专门优化的产品。而且从部署角度一个 ES 集群本身就不轻你再叠加 k-NN 的额外内存消耗成本不会低。适合本身就在用 ES、不想引入新组件的团队。2.7 LanceDB嵌入式向量库的新派LanceDB 是这几款里比较新的面孔主打嵌入式部署。它没有独立服务端直接在 Python 进程内读写向量数据底层存储是 Lance 列式格式。这个设计非常有意思相当于把向量数据库的能力做成一个本地文件格式数据和元数据放在同一套存储里。LanceDB 和 DuckDB 这类分析引擎可以无缝互通你可以用 SQL 直接查询 Lance 文件里的向量数据做分析实验非常顺手。它天然支持多模态数据比如图片指针、文本内容、JSON 元数据都能放在同一条记录里。但是要注意嵌入式架构带来的代价是没有内置的网络访问层、权限控制、多租户能力。如果你的应用是分布式的多个服务需要并发访问同一个向量库用 LanceDB 需要自己在外面封装服务层。它更适合单机分析、本地文件型应用或者作为大模型应用里单机推理时的向量存储。2.8 Neo4j图数据库的向量能力拓展Neo4j 出现在这个名单里很多人会意外但它从 5.11 版本开始引入了向量索引和余弦相似度搜索功能。你可以给 Node 的属性建向量索引然后用 Cypher 查询做近邻检索。这对做知识图谱 向量混合检索的场景很有价值。举一个实际例子做智能问答的时候既需要图谱里的关系路径比如A 和 B 有什么关系又需要语义检索比如和这段话意思相近的文本有哪些。以前需要维护两个系统图谱用 Neo4j、向量用其他数据库现在 Neo4j 一个库就能同时处理两种查询省掉了数据同步的麻烦。但 Neo4j 的向量能力还是偏基础它不支持复杂的向量索引类型主要就是余弦相似度近邻查询。向量规模如果特别大性能和专门的向量数据库有差距。它的定位是图数据库的增强功能而不是一个完整的企业级向量检索平台。如果你本来就用 Neo4j可以顺手用它的向量能力如果还没用图数据库也没必要为了向量功能专门引入它。3. 从部署到运维Milvus 和 Attu 的实操经验3.1 Milvus 安装部署的两种方式Milvus 的部署是这一块的高频提问点尤其是热词里出现milvus安装和docker安装pgvector可见很多人都卡在第一步。先说 Milvus 的部署官方提供 Standalone 和 Cluster 两种模式。本地开发和测试直接用 Standalone 就够了用 Docker Compose 把 Milvus、etcd、MinIO 三个服务拉起来就行。我习惯用官方提供的 docker-compose.yml。注意几个容易踩的坑etcd 和 MinIO 的数据目录要映射到宿主机持久化否则容器重建数据全没了Milvus 容器启动后要等大概二三十秒才能连上因为要初始化元数据和存储连接如果你本机端口已经被占用需要改 compose 里的端口映射尤其 19530 和 9091。Milvus 2.4 之后的版本默认的日志消息存储换成了 Kafka 或 Pulsar在 Standalone 模式下这些组件也会被自动拉起来。不过我实际测试时发现如果只做单机实验可以把消息存储相关配置简化否则一次 docker compose up 要拉几个 G 镜像机器配置低会很痛苦。3.2 Attu 版本匹配与连接群里有人问attu 支持哪个 milvus 版本这个问题值得认真回答。Attu 是 Milvus 的图形化管理界面类似数据库的 Navicat。它分为 Web 版和桌面版Web 版可以 docker run 启动桌面版直接装客户端。Attu 的版本和 Milvus 的版本并不是完全割裂的它有自己的 Release 周期。官方文档里一般会说明某个 Attu 版本支持哪个 Milvus 版本。经验是Attu 2.3 支持 Milvus 2.3Attu 2.4 支持 Milvus 2.4大版本最好跟 Milvus 对齐。如果你用 Milvus 2.3.x装了最新 Attu 2.4可能遇到连接超时或元数据展示不全的问题。连接 Attu 的时候填写地址要特别注意如果你是在 Docker 里通过网络方式启动 Attu连接的是宿主机上的 Milvus地址要填宿主机的容器 IP 或局域网 IP而不是 localhost。这一点新手特别容易卡住。还有Milvus 默认端口 19530 是 gRPC 端口不是 Attu 的 HTTP 端口Attu 通过 gRPC 连 Milvus所以端口不要填错。3.3 与 LangChain4j 集成的要点热词里面出现了langchain4j milvus现在 Java 生态里用 LangChain4j 接 Milvus 的团队越来越多了。LangChain4j 的 milvus 模块封装了 Milvus Java SDK你只需要在项目里引入依赖然后配置 MilvusEmbeddingStore。连接参数最关键的是 host、port、collectionName。注意 collection 不存在时 LangChain4j 会自动创建但默认维度只有 384如果你后端用的 embedding 模型是 1024 维或者 1536 维必须在创建时显式设置 dimension否则写入数据会直接报维度错误。还有一个小坑LangChain4j 的 MilvusEmbeddingStore 在写入 embedding 时会默认建立索引但如果你使用自定义 collection 名字和模型维度不一致时很容易遇到dimension mismatch报错。解决办法是提前用 Attu 手动创建 collection或者在代码里明确指定 dimension 和 index 参数。4. Chroma 集合背后的一堆表到底都是干嘛的4.1 Chroma 存储结构拆解热词里有用向量数据库 chroma 添加集合后产生了很多表各个表含义、关联关系解释下这是典型的坑。Chroma 默认使用的持久化方式是 SQLite你会在数据目录下看到一个 chroma.sqlite3 文件。用 SQLite 工具打开后会发现里面有不少表第一次看容易懵。先说核心表collections 表保存集合信息每一行对应一个集合字段包括 id、name、metadata_json。embeddings 表是核心数据表保存所有向量和文本内容它的 collection_id 字段关联到 collections 表的 id。围绕 embeddings 表Chroma 还建了 embedding_metadata 表这个表的名字很像动态生成的实际上它就是按集合进行分表处理的结果保存的是每个 embedding 的元数据键值对。如果你每次创建 collectionChroma 就会生成相应的 metadata 表和 embedding 表所以集合多了之后 SQLite 里的表数量会明显增加。打开库会发现很多以 embedding_metadata_ 开头的表这就是不同集合各自的元数据表。比如你添加了一个叫 doc_collection 的集合Chroma 内部会生成 doc_collection 对应的 embedding 数据存储表和 metadata 表SQLite 里看到的名称可能是一个带哈希后缀的表名。这个命名规则不同版本略有差异但核心思路没变collection - embeddings - metadata 是一条完整链路。4.2 表之间的关联关系和生命周期从实际使用角度表关联关系可以这样理解collections.id 是主键embeddings 表通过 collection_id 关联到 collections。当你要查询某个集合里的所有向量实际上就是 SELECT * FROM embeddings WHERE collection_id ?。而每条 embedding 的附加信息比如来源文档、页码、时间戳都存储在对应的 metadata 表中通过 embedding_id 关联回 embeddings.id。很多人在 Chroma 里添加了 collection 后发现 SQLite 里出现了好几个计数表比如 max_embedding_id 这类。这些表的作用是维护自增 ID 的分配状态确保并发写入时不会产生重复 ID。如果你直接操作 SQLite 删数据会导致这些计数表不同步进而引发数据一致性问题。所以官方 API 之外的手工操作一定要谨慎。还有一个实用的理解方式Chroma 的集合和 SQLite 表不是严格的一对一关系。同一个集合的数据可以分布在多个分段表里Chroma 内部会维护一个分段映射机制数据量增长后可能会分裂。这也是为什么你会看到类似的表名带不同后缀的原因。调试时如果发现某个集合查不到数据可以用 SQLite 工具看一下 collections 表里对应该集合的行确认 collection ID 是否匹配而不是盲目认为数据丢了。5. pgvector 如何结合知识库一个完整的落地案例5.1 表结构与向量索引设计pgvector 结合知识库是很多后端团队的常规路径。典型的表结构设计很简单我通常这样建表CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE doc_chunks ( id BIGSERIAL PRIMARY KEY, content TEXT NOT NULL, source_url TEXT, chunk_index INTEGER, embedding vector(1536), created_at TIMESTAMP DEFAULT now() );embedding 字段的维度取决于你到底用什么模型做向量化。如果用 OpenAI 的 text-embedding-3-small就是 1536 维如果用 BGE 系列通常 768 或者 1024。这里要提前和模型对齐因为向量维度一旦写入就不能随意修改除非重建表。建完表之后要建向量索引。我强烈建议用 HNSWCREATE INDEX ON doc_chunks USING hnsw (embedding vector_cosine_ops);使用 cosine 操作符是因为我们做文本相似度检索时通常用余弦距离。如果你做的是图片或者其它类型可以换成 vector_l2_ops 或 vector_ip_ops。HNSW 的参数 m 和 ef_construction 可以在索引创建时指定默认值 16 和 64 对大部分情况已经足够追求更高召回率可以调大 ef_construction但建索引会变慢。5.2 实现流程和关键 SQL知识库的完整流程分三步文档解析、数据入库、查询检索。文档解析阶段把 PDF、Word、Markdown 等内容切成固定长度的 chunk一个 chunk 大概两三百字。切片策略要根据业务调整太长了检索粒度粗太短了上下文信息不足。然后调用 embedding 接口把每个 chunk 转成向量连同原文和元数据一起插入 pgvector 表。数据入库阶段可以批量插入比如一条 INSERT 语句里带多个值会比逐条插入快很多。pgvector 在批量写入时HNSW 索引是在后台同步更新的不会阻塞查询这点比 IVFFlat 要舒服。查询检索阶段核心 SQL 长这样SELECT content, source_url, chunk_index, 1 - (embedding $1) AS similarity FROM doc_chunks WHERE source_url $2 ORDER BY embedding $1 LIMIT 10;这个 SQL 的含义是先在符合 source_url 条件的记录里按向量距离从小到大排序取前 10 条最相似的。如果数据量大HNSW 索引会自动生效不需要手动指定 hint。有一点要注意 是余弦距离操作符类似embedding $1返回的是距离值越小越相似。如果你想要相似度分数用 1 - distance 转换一下。实测下来百万级数据的知识库用 pgvector HNSW 做查询延迟通常在 10ms 以内完全可以满足大多数聊天机器人、RAG 问答的场景。不需要为了追求技术新奇专门上一套复杂系统。5.3 pgvector 的坑和取舍pgvector 也有几个典型的坑我用下来印象最深的是vector 字段的维度不能随便改HNSW 索引的构建内存消耗不小PostgreSQL 的 autovacuum 性能管理要处理大量删除更新。如果你要删除一批旧文档然后重新写入会产生大量 dead tuple查询性能会突然下降需要手动 VACUUM ANALYZE。这在前期的设计阶段就要考虑比如通过 source_url 字段做逻辑删除或者定期重建表来避免碎片堆积。还有一点是 pgvector 不太适合超大规模比如上亿向量的多节点分片场景。虽然 PG 本身有分区表和扩展能力但向量检索的海量高并发热点场景独立的分布式向量库在路由和负载均衡上更专业。所以我的建议很明确百万级项目用 pgvector 非常合适千万级以上老老实实选 Qdrant 或 Milvus。6. 常见问题排查实录6.1 Milvus 安装后 etcd 问题的排查Milvus 启动阶段最常遇到的问题就是 etcd。很多人在本地启动 Milvus 后服务好像正常但连接总是报错查看容器日志发现 etcd 不断重启。这个问题的根源通常是 etcd 数据目录权限或者端口冲突。etcd 默认使用 2379 端口如果你本机有其他服务占用Milvus 初始化时就无法写入元数据。排查思路很简单先用 docker ps 查看所有容器状态确认 etcd 容器是否正常运行再用 docker logs 查看 etcd 日志看有没有 permission denied 或 connection refused 的报错然后检查 docker-compose.yml 里 etcd 的端口映射是否被占用。处理办法通常是把这几个容器彻底清理删除旧 volume然后重新 up因为 etcd 元数据一旦损坏很难自行修复。另外一个常见问题是 etcd 和 MinIO 的地址配置错误。在 docker-compose 方式部署时容器内部的 hostname 是服务名比如 etcd 的地址应该是 http://etcd:2379而不是 localhost。有些人在自定义配置时误用 localhost导致 Milvus 无法连接 etcd。这类问题在日志里会明确报出 etcd 连接失败排查起来相对直接。6.2 Docker 部署 pgvector 的注意事项docker 安装 pgvector 很简单官方镜像直接带上这个扩展。你可以用pgvector/pgvector:pg16这样的镜像标签它会定期更新适配不同版本的 PostgreSQL。用 Docker 跑 pgvector 时最需要注意的是数据卷持久化。默认容器有一个 /var/lib/postgresql 目录需要把它映射到宿主机目录或卷里否则容器重启后数据彻底丢了。启动命令大概是这样docker run -d \ --name pgvector-demo \ -e POSTGRES_USERpostgres \ -e POSTGRES_PASSWORDpostgres \ -e POSTGRES_DBknowledge \ -p 5432:5432 \ -v pgdata:/var/lib/postgresql \ pgvector/pgvector:pg16连接上来之后先执行 CREATE EXTENSION IF NOT EXISTS vector; 如果提示扩展不存在说明镜像有问题或 PG 版本太老。另外如果你在别的容器里访问这个数据库host 要填宿主机 IP不能填 localhost如果用 docker-compose 并在同一个网络内可以直接用服务名。6.3 选型决策速查表把前面说的这些信息浓缩成一张表方便你在方案评审时直接翻出来用数据库定位数据规模建议部署复杂度适合场景主要限制Milvus企业级分布式向量数据库千万级以上高大规模生产环境、复杂过滤、多团队协作组件多运维成本高Qdrant高性能独立向量数据库百万到千万级中生产级语义检索、Filter Vector 查询功能相对集中Weaviate自带向量化的全能型百万到千万级中不想维护 embedding 链路需要混合检索GraphQL 学习成本Chroma轻量嵌入式万到百万级极低原型验证、教学、本地小工具分布式与并发能力弱pgvectorPostgreSQL 扩展百万级左右极低已有 PG 体系、中小规模知识库超大规模扩展有限OpenSearch搜索生态向量增强百万到千万级较高已有 ES/OpenSearch 体系混合检索非专业向量存储LanceDB嵌入式分析型万到百万级极低本地文件型应用、与分析框架结合无独立服务端Neo4j图数据库向量扩展数十万级中图谱向量混合场景向量功能较基础这张表只是参考具体选型还是要拿你的真实数据量和查询模式去验证。我这里给出的都是比较保守的经验值因为实际效果跟索引参数、机器配置、数据特征都有关系盲信宣传数字不如自己做一轮压测来得靠谱。我个人在实际操作中的体会是向量数据库选型没有绝对的最优只有最合适。业务还在早期阶段优先选简单方案比如 pgvector 或 Chroma快速跑通流程等数据量和并发真正上来之后再迁移到 Qdrant 或 Milvus。迁移的过程虽然麻烦但总比一开始就背上一套重型系统要稳妥得多。数据规模小于百万时简单系统的优势远大于那些高性能参数带来的纸面收益。