← 返回资讯

向量数据库选型:Chroma / Milvus / pgvector / Qdrant

2026-07-03

向量数据库的选型不是”哪个最强”,而是”哪个最匹配你当前阶段”。开发期用重型生产库会拖慢迭代速度,生产上了流量再用玩具库会扛不住。核心维度:数据规模 × 运维成本 × 功能需求。

我见过两种典型的踩坑路径,你多半也遇到过其中一种。第一种是”重装上阵”:项目刚立项就上 Milvus 分布式集群,结果知识库才 2 万条文档,团队一半精力耗在维护 etcd、MinIO、Pulsar 这套依赖上,需求还在天天变,谁都不敢动索引结构。第二种是”轻装冒进”:Chroma 里跑了半年原型,数据涨到 300 万条,某天线上检索延迟从 80ms 飙到 2 秒,一查是单机 HNSW 内存放不下,扩容只能整库重建。这两种坑的根子都一样:选型时只看了”能不能跑起来”,没看清”这条路能走多远”。下面按维度拆开讲,讲完你应该能照着自己的数据量和团队现状直接对号入座。

主流向量数据库对比

数据库定位最大规模部署复杂度原生混合检索多租户语言 SDK
Chroma开发/原型<500 万向量极低(pip install)基础Python/JS
pgvector中等规模千万级低(PG 插件)半(pg 全文)基础全语言(PG 驱动)
Qdrant中大规模亿级中(单二进制)是(v1.7+)Python/Rust/JS/Go
Milvus大规模百亿级高(分布式)是(v2.4+)Python/Java/Go
Weaviate中大规模亿级是(BM25F)Python/JS/Go
Elasticsearch搜索+向量亿级是(成熟)全语言

表里”部署复杂度”这一栏最容易被低估。它不只是”装起来要几步”,而是长期运维成本:Chroma 是一个进程内嵌库,坏了重启就行,几乎没有运维负担;Qdrant 是单二进制,一个 docker run 就能起服务,配置文件也就几十行;Milvus 官方 Helm Chart 拉起来至少要 etcd(元数据)、MinIO(对象存储)、Pulsar 或 Kafka(消息队列)三个依赖组件,任何一个挂了整个集群都会受影响,排障链路比前两者长一个数量级。所以选型前先问自己一句:团队里有没有人愿意长期盯着这套分布式系统的监控告警?没有的话,宁可先用 Qdrant 单机版撑到数据量真的顶不住再迁移。

按阶段选型建议

开发原型阶段 → Chroma

零配置,内存或本地文件持久化,LangChain/LlamaIndex 默认支持:

import chromadb
from chromadb.utils import embedding_functions

client = chromadb.PersistentClient(path="./chroma_db")
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
    api_key="sk-xxx",
    model_name="text-embedding-3-small"
)
collection = client.get_or_create_collection("my_docs", embedding_function=openai_ef)

# 插入
collection.add(documents=["文档内容..."], ids=["doc_1"])

# 检索
results = collection.query(query_texts=["用户问题"], n_results=5)

注意:Chroma 不支持原生混合检索,ANN 实现是 HNSW(单机),不适合生产多副本场景。

上面这段代码里有两个容易被忽略的点。第一,PersistentClient(path="./chroma_db") 落盘的是 SQLite + 本地二进制索引文件,如果你把这个目录部署到 Serverless 环境(比如某些无状态容器平台),容器重启数据就没了,本地持久化只适合单机长驻进程,不适合弹性伸缩的部署形态。第二,embedding_function 绑定在 collection 创建时,后续如果你换了 embedding 模型(比如从 text-embedding-3-small 换成别的模型),旧数据的向量维度和语义空间对不上,必须整库重新计算 embedding 再插入,不能新旧数据混用一个 collection。这个坑在换模型降本的场景里很常见,提前建两个 collection 分开验证再切流量,比线上直接覆盖安全得多。

HNSW 参数到底怎么调

不管是 Chroma、pgvector 还是 Qdrant,主流 ANN(近似最近邻)索引都是 HNSW,理解它的几个参数能帮你在”检索速度”和”召回准确率”之间做取舍,而不是靠默认值蒙:

参数作用调大的代价什么时候需要调
m每个节点的最大连接数,决定图的稠密度内存占用和构建时间线性上升数据维度高(如 3072 维)、召回率要求高时调到 32~48
ef_construction建索引时候选队列大小,越大索引质量越高构建时间显著变长(对百万级数据可能从分钟级到小时级)一次性建库、后续变更少的场景可以调大到 128~256
ef_search(查询时的 ef检索时候选队列大小,越大召回越准每次查询延迟线性上升召回率不达标时优先调这个,不用重建索引,是最省事的调优手段

实操顺序建议:先用默认参数(m=16, ef_construction=64)建好索引,线上跑一段时间统计召回率(可以用一批已知答案的问题做人工评估集),如果召回不够再优先调 ef_search——这个参数是查询时传入的,不需要重建索引,改完立刻生效,成本最低;如果调到很大召回还是不理想,说明问题可能出在 embedding 模型或者 chunk 切分策略上,而不是 ANN 参数,别在这个方向上继续加码。

已有 PostgreSQL → pgvector

团队已有 PG 运维能力时,pgvector 是最低迁移成本的选择:

-- 安装扩展后建表
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    content TEXT,
    embedding vector(1536),  -- text-embedding-3-small 维度
    source TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 创建 HNSW 索引(v0.5+ 支持,比 ivfflat 构建更快、精度更稳定)
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

-- 向量检索
SELECT content, 1 - (embedding <=> $1::vector) AS similarity
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT 5;

适合规模:百万级向量、已有 PG 基础设施,不需要分布式扩展。

这里 <=> 是 pgvector 注册的余弦距离运算符(还有 <-> 欧氏距离、<#> 内积,按你的 embedding 模型推荐的相似度算法选,OpenAI 系模型一般用余弦距离),1 - 距离 换算成”相似度”只是方便展示,真正参与排序的是 ORDER BY embedding <=> $1::vector 这一行,千万别在应用层再排一遍序,会白白多一次全量扫描。另外一个真实会踩的坑是:vector(1536) 这个维度定死在建表语句里,如果你后面把 embedding 模型换成 1024 维或 3072 维的,插入时会直接报错 expected 1536 dimensions, not 1024,报错信息很直白,但很多团队第一反应是以为 pgvector 扩展坏了,其实是列定义和新模型维度对不上,解决办法只能是新建一张表迁移数据,pgvector 不支持在线修改向量维度。

还有个容易被忽略的运维细节:HNSW 索引在 pgvector 里是”事后建”的,如果你先批量插入几百万条数据再执行 CREATE INDEX,会比边插入边维护索引快得多——因为增量维护索引的写放大成本很高。所以批量导入历史数据时,正确顺序是:先建表不建索引 → 全量导入 → 最后统一建 HNSW 索引,这样能省下不少构建时间。

生产中大规模 → Qdrant

单二进制部署,运维比 Milvus 简单,功能完整:

from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance, PointStruct

client = QdrantClient("localhost", port=6333)

# 建集合
client.create_collection(
    collection_name="knowledge",
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)

# 插入向量
client.upsert(
    collection_name="knowledge",
    points=[
        PointStruct(id=1, vector=[...], payload={"content": "文档内容", "source": "doc.pdf"})
    ]
)

# 检索
results = client.search(
    collection_name="knowledge",
    query_vector=[...],
    limit=20,
    with_payload=True
)

上面代码里 client.search 传入 limit=20 是故意留了余量——实际业务里常见的模式是”检索 Top-20,再用 Rerank 模型精排出 Top-5 喂给大模型”,直接检索 Top-5 容易漏掉语义相关但表述不同的段落,这也是为什么很多 RAG 系统的检索环节会先”多取一点,再精筛”。Qdrant 从 v1.7 开始支持在同一个集合里做混合检索(稠密向量 + 稀疏向量融合排序),如果你的知识库里有大量专有名词、型号编码这类关键词检索比语义检索更管用的内容,混合检索能明显提升准确率,具体怎么权衡关键词和语义检索的比重,可以参考 混合检索:关键词+向量 这篇里的实测数据。

超大规模分布式 → Milvus

数据量亿级以上,需要水平扩展和高可用:

from pymilvus import connections, Collection, CollectionSchema, FieldSchema, DataType

connections.connect("default", host="localhost", port="19530")

# 建集合(Schema 定义较繁琐,但灵活)
fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True),
    FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536),
    FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=4096),
]
schema = CollectionSchema(fields)
collection = Collection("knowledge", schema)

Milvus 的 Schema 定义看着繁琐,其实是在为分布式做准备:每个字段的类型和长度都要提前声明清楚,是因为底层要把数据分片(shard)到多个 Query Node 上并行检索,Schema 越明确,分片和查询计划就越可控。这也是它能撑到百亿级向量的根本原因——不是索引算法比别家先进,而是整套架构从一开始就是为水平扩展设计的。反过来说,如果你的数据量根本用不到分片(比如几十万到几百万条),上 Milvus 就是在为用不上的扩展能力支付运维成本,这时候 Qdrant 或 pgvector 的性价比明显更高。

判断”是否真的需要 Milvus”有个简单的经验法则:单机内存能不能放下全部向量索引。1536 维的 float32 向量,每条大约占 6KB,1000 万条大约 60GB,如果你的服务器内存够用(比如 128GB 以上),Qdrant 单机也能扛住千万级检索,不需要跳到分布式架构;只有当数据量逼近或超过单机内存上限,或者对可用性要求高到必须做多副本容灾,才真正到了 Milvus 的适用区间。

迁移注意事项

从 Chroma 迁移到 Qdrant/Milvus 时,向量不需要重新计算(embedding 结果是确定性的),只需导出原始向量和 payload,重新 upsert 到新库即可。迁移脚本耗时取决于数据量,百万级通常在 30 分钟以内。

常见问题

pgvector 和 Qdrant 怎么选? 核心判断是:团队是否已经运维 PostgreSQL。有 PG 基础设施就用 pgvector,直接复用备份、监控、权限体系;没有的话,Qdrant 的纯向量搜索性能和功能更完整,运维成本也低于 Milvus。

向量数据库需要备份吗? 必须。索引阶段的 embedding 计算有 API 成本,知识库是”可再生但昂贵”的资产。Qdrant 支持快照(snapshot),pgvector 走 PG 标准备份,Milvus 有 milvus-backup 工具。

过滤(Filter)和向量检索能同时用吗? 可以,这是”元数据过滤+向量检索”的常见模式。大多数向量数据库都支持在检索时附加元数据过滤条件(如 source = "产品手册" 只在指定文档范围内检索),但性能表现因实现方式不同,建议实测。这里要分清”前置过滤”和”后置过滤”两种实现方式:前置过滤是先按条件筛出候选集再做向量检索,后置过滤是先做向量检索再对结果集按条件过滤——如果你的过滤条件命中率很低(比如只有 1% 的文档匹配 source),用后置过滤经常会出现”检索到 20 条结果,过滤完只剩 1 条甚至 0 条”的情况,这时候应该确认数据库用的是前置过滤模式,或者手动把 limit 调大来补偿,Qdrant 和 Milvus 默认都是前置过滤,pgvector 需要你自己在 SQL 里把 WHERE 条件写在向量排序之前才能让查询规划器走索引。

连接向量数据库报超时或连接拒绝怎么排查? 先分清是网络问题还是服务本身没起来。Qdrant/Milvus 部署在容器里时,最常见的报错是 Connection refused,八成是端口没映射对(Qdrant 默认 6333 是 REST,6334 是 gRPC,容易映射错端口)或者服务还没完全初始化完成就开始连接,Milvus 尤其明显,它启动后要等 etcd 和 MinIO 都健康检查通过才对外提供服务,容器编排里最好加上启动依赖和健康检查等待,而不是固定 sleep 几秒就假设服务已就绪。如果是跨机房或者公网访问,超时更可能是安全组/防火墙没放通端口,先用 telnetnc 测端口连通性,别一上来就怀疑是 SDK 版本问题。

索引构建太慢,卡在几十万条数据要跑很久怎么办? 先看是不是 ef_constructionm 设太大——这两个参数和构建时间基本是线性甚至更高阶的关系,百万级数据下 ef_construction 从 64 调到 256,构建时间可能翻两三倍。其次检查是不是每插入一条就触发一次索引重建(增量写入模式下这是正常现象,但如果你是批量导入历史数据,前面 pgvector 那节提到的”先导数据后建索引”同样适用于 Qdrant、Milvus,能省下大量重复计算的时间)。如果单机 CPU 核数不够,HNSW 构建本身是可以并行的,检查一下客户端或服务端有没有开启多线程构建的配置项,很多时候慢不是算法问题,而是并行度没开够。

向量数据库这块的成本大概怎么估? 成本主要来自两块:一是 embedding 计算的 API 调用费用,这个和你选的模型直接相关,具体单价和不同厂商的换算方式建议直接查 Embedding 选型 里的实测对比,价格更新较快,以官方计费页为准;二是数据库本身的资源占用,自建 Qdrant/Milvus 主要是服务器内存和磁盘成本(内存按向量数 × 维度 × 4 字节再加上 HNSW 图结构的额外开销粗估,实际会比”裸向量大小”多出 30%~50%),托管服务(如 Zilliz Cloud、Qdrant Cloud)则是按存储量和请求量计费,具体价格截至 2026-06 各家浮动较大,做预算前一定要拿自己的真实数据量去官网算一遍,不要凭感觉估。


← 返回 应用模式总览:从 Prompt 到 Agent | 应用模式专题

相关阅读:RAG Embedding 选型 · 混合检索:关键词+向量 · RAG 怎么做:架构与落地

向量库切换、embedding 接口统一管理?力达云聚合 API 帮你把接入层标准化,降低供应商切换成本。