向量数据库选型:Chroma / Milvus / pgvector / Qdrant
向量数据库的选型不是”哪个最强”,而是”哪个最匹配你当前阶段”。开发期用重型生产库会拖慢迭代速度,生产上了流量再用玩具库会扛不住。核心维度:数据规模 × 运维成本 × 功能需求。
我见过两种典型的踩坑路径,你多半也遇到过其中一种。第一种是”重装上阵”:项目刚立项就上 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 几秒就假设服务已就绪。如果是跨机房或者公网访问,超时更可能是安全组/防火墙没放通端口,先用 telnet 或 nc 测端口连通性,别一上来就怀疑是 SDK 版本问题。
索引构建太慢,卡在几十万条数据要跑很久怎么办?
先看是不是 ef_construction 或 m 设太大——这两个参数和构建时间基本是线性甚至更高阶的关系,百万级数据下 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 帮你把接入层标准化,降低供应商切换成本。