向量数据库选型实战:Milvus vs Qdrant vs pgvector 的取舍
做 RAG 项目绕不开向量数据库选型。市面上选择一大把——Milvus、Qdrant、pgvector、Weaviate、Chroma……光看官网都说自己最好。这篇复盘我们实际跑了三个主流方案的过程和结论,不讲玄学,只讲实测数据和生产经验。
选型背景
先说项目情况,选型脱离业务就是耍流氓:
| 维度 | 实际情况 |
|---|---|
| 向量规模 | 500 万条,预计增长到 2000 万 |
| 维度 | 1024 维(BGE-large-zh) |
| 检索模式 | 混合检索(向量 + BM25 关键词) |
| 并发 | 峰值 50 QPS |
| 延迟要求 | P99 < 50ms |
| 运维能力 | 小团队,没专职 DBA |
| 已有基础设施 | PostgreSQL 主库 + Redis |
三个候选的技术路线
先理解它们在设计哲学上的根本差异,这决定了各自适合的场景:
┌─────────────────────────────────────────────────────────────────┐
│ 三种技术路线对比 │
├────────────────┬────────────────┬───────────────────────────────┤
│ Milvus │ Qdrant │ pgvector │
│ 分布式专用 │ 单机轻量专用 │ PostgreSQL 扩展 │
├────────────────┼────────────────┼───────────────────────────────┤
│ ┌────────────┐ │ ┌────────────┐ │ ┌──────────────────────────┐ │
│ │ 分布式架构 │ │ │ Rust 单机 │ │ │ 在 PG 里加向量类型 │ │
│ │ 存算分离 │ │ │ 内存索引 │ │ │ 复用 PG 的事务/索引/SQL │ │
│ │ 多节点集群 │ │ │ WAL 持久化 │ │ │ HNSW + IVFFlat 索引 │ │
│ └────────────┘ │ └────────────┘ │ └──────────────────────────┘ │
│ │ │ │
│ 强项: │ 强项: │ 强项: │
│ 海量数据 │ 中小规模极速 │ 与业务数据同库 │
│ 水平扩展 │ 部署简单 │ 事务一致性 │
│ 生态丰富 │ 资源占用低 │ 运维成本低 │
│ │ │ │
│ 弱项: │ 弱项: │ 弱项: │
│ 运维复杂 │ 分布式要自己搞 │ 千万级以上性能下降 │
│ 资源占用大 │ 集群版不够成熟 │ 占用 PG 资源 │
│ 小项目杀鸡用 │ 生态较新 │ 向量检索非原生优化 │
│ 牛刀 │ │ │
└────────────────┴────────────────┴───────────────────────────────┘性能压测对比
用同一批数据(500 万条 1024 维向量)做压测,结果如下:
检索延迟(QPS=50,Top-10 召回)
| 指标 | Milvus (HNSW) | Qdrant (HNSW) | pgvector (HNSW) |
|---|---|---|---|
| P50 延迟 | 8ms | 5ms | 15ms |
| P95 延迟 | 18ms | 12ms | 35ms |
| P99 延迟 | 28ms | 20ms | 52ms |
| 召回率 | 98.2% | 97.8% | 96.5% |
写入性能
| 指标 | Milvus | Qdrant | pgvector |
|---|---|---|---|
| 批量写入 QPS | 8,000 | 12,000 | 3,000 |
| 单条写入延迟 | 2ms | 1ms | 5ms |
资源占用
| 指标 | Milvus | Qdrant | pgvector |
|---|---|---|---|
| 内存占用 | 6.2 GB | 3.8 GB | 5.5 GB |
| 磁盘占用 | 4.1 GB | 2.9 GB | 4.8 GB |
| 额外组件 | etcd + MinIO | 无 | 无(复用 PG) |
数据量增长后的表现
这是最关键的测试——500 万到 2000 万向量的性能变化:
延迟随数据量变化(P99 延迟,QPS=50)
延迟(ms)
100 │ · pgvector
│ ····
80 │ ····
│ ····
60 │ ····
│ ····· ····
40 │ ····· ···· · Milvus
│ ·······
20 │ · · · · · · · · · · · · · · · Qdrant
│
0 └──────┬──────┬──────┬──────┬──────┬──────
200万 500万 1000万 1500万 2000万
数据量
结论:
- Qdrant:数据量增长对延迟影响最小,曲线最平
- Milvus:百万到千万级稳定,分布式优势开始体现
- pgvector:千万级后延迟陡增,HNSW 索引构建也变慢混合检索的实现差异
我们的需求是向量 + BM25 关键词混合检索,三个方案的实现方式差别很大:
Milvus:2.4+ 支持混合检索
from pymilvus import Collection
# Milvus 2.4+ 支持稀疏向量 + 稠密向量混合检索
collection.search(
data=[dense_vector], # 稠密向量
anns_field="dense_vector",
param={"metric_type": "IP", "params": {"ef": 64}},
sparse_vectors={sparse_vector}, # 稀疏向量(BM25)
limit=10
)优点:内置支持,不用外挂。缺点:稀疏向量检索能力不如专业 BM25 引擎。
Qdrant:原生支持 Payload 过滤
from qdrant_client import QdrantClient
client.search(
collection_name="docs",
query_vector=embedding,
query_filter={
"must": [
{"key": "category", "match": {"value": "技术"}},
{"key": "tags", "match": {"any": ["RAG", "向量数据库"]}}
]
},
limit=10
)优点:Payload 过滤极快,适合「向量 + 属性」的混合检索。缺点:不是真正的 BM25 全文检索,是属性过滤。
pgvector:SQL 原生联合查询
-- 向量相似度 + 全文检索联合查询,一条 SQL 搞定
SELECT id, content,
1 - (embedding <=> $1) AS vector_score,
ts_rank(tsv, plainto_tsquery($2)) AS text_score
FROM documents
WHERE embedding <=> $1 < 0.5 -- 向量相似度过滤
AND tsv @@ plainto_tsquery($2) -- 全文匹配
ORDER BY (1 - (embedding <=> $1)) * 0.7 +
ts_rank(tsv, plainto_tsquery($2)) * 0.3 DESC
LIMIT 10;优点:向量 + BM25 + 业务条件一条 SQL 搞定,事务一致性强。缺点:大数据量下 SQL 执行计划可能不理想。
最终选型决策
综合评估后的决策矩阵:
| 评估维度 | 权重 | Milvus | Qdrant | pgvector |
|---|---|---|---|---|
| 检索性能 | 30% | 9 | 10 | 7 |
| 写入性能 | 10% | 8 | 9 | 6 |
| 运维成本 | 25% | 5 | 8 | 10 |
| 混合检索 | 20% | 8 | 7 | 10 |
| 扩展性 | 15% | 10 | 7 | 5 |
| 加权总分 | 100% | 7.55 | 8.35 | 7.85 |
我们的选择:Qdrant
原因:
- 500 万数据量 Qdrant 完全 hold 住,延迟表现最好
- 部署最简单,单个 Docker 容器就跑起来了,没有 etcd/MinIO 这些额外依赖
- 资源占用最低,小团队服务器资源有限
- Payload 过滤满足业务需求,虽然不是真 BM25,但配合 ES 做关键词检索够用
- 未来 2000 万时如果 Qdrant 扛不住,再迁 Milvus,迁移成本可控
什么情况选另外两个
选 Milvus 的场景:
- 数据量确定超过 5000 万
- 需要多副本、高可用集群
- 有专职运维团队
- 对检索延迟有极致要求(P99 < 10ms)
选 pgvector 的场景:
- 数据量在 500 万以内
- 已经在用 PostgreSQL
- 向量检索和业务查询强关联(需要 JOIN 业务表)
- 团队不想引入新组件
- 项目初期快速验证
生产落地踩的坑
坑 1:HNSW 索引参数没调,默认参数性能差
Qdrant 默认的 HNSW 参数偏保守,建索引后检索延迟比预期高。
解决:hnsw_config 调优,m 和 ef_construct 是核心参数。
client.update_collection(
collection_name="docs",
hnsw_config={
"m": 32, # 每层最大连接数,越大精度越高、内存越多
"ef_construct": 256, # 构建时搜索宽度,越大索引质量越好
}
)
# 检索时 ef 参数
client.search(
collection_name="docs",
query_vector=embedding,
search_params={"hnsw_ef": 128, "exact": False},
limit=10
)坑 2:全量重建索引时服务不可用
一开始用「删集合 → 重建 → 导入」的方式更新向量,重建期间服务全挂。
解决:用双集合切换。新建 docs_v2,导入完成后原子切换别名指向。
# 创建新集合
client.recreate_collection("docs_v2", ...)
# 导入数据到 docs_v2
# ...
# 切换别名
client.update_collection_aliases(
actions=[
DeleteAliasAction("docs"),
CreateAliasAction("docs", "docs_v2")
]
)
# 删除旧集合
client.delete_collection("docs_v1")坑 3:pgvector 的 HNSW 索引构建慢到怀疑人生
500 万向量建 HNSW 索引花了 6 小时,期间 CPU 100%。
解决:pgvector 适合数据量小的场景,大索引构建用 maintenance_work_mem 调大 + 并行构建。
SET maintenance_work_mem = '2GB';
SET max_parallel_maintenance_workers = 4;
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 128);坑 4:Milvus 的 etcd 成为单点
Milvus 依赖 etcd 存元数据,etcd 挂了整个集群不可用。
解决:etcd 必须部署集群(至少 3 节点)。这也是小团队不推荐 Milvus 的原因——运维成本高。
复盘总结
向量数据库选型没有「最好」,只有「最合适」。核心判断依据就两条:
- 数据量级:500 万以下 pgvector 够用;500 万到 5000 万 Qdrant 最佳;5000 万以上上 Milvus
- 运维能力:有 DBA 选 Milvus;小团队选 Qdrant;已有 PG 且不想加组件选 pgvector
技术选型决策树:
数据量级?
│
┌───────────┼───────────┐
▼ ▼ ▼
< 500万 500万~5000万 > 5000万
│ │ │
▼ ▼ ▼
已有 PG? Qdrant Milvus
│ │
┌──┴──┐ 有专职运维?
是 否 │
│ │ ┌───┴───┐
▼ ▼ 是 否
pgvector Qdrant ▼ ▼
Milvus Qdrant
(先顶住)最后一句:选型不要看官网的 benchmark,要用自己的真实数据跑压测。每家官网都只展示自己擅长的场景,只有你的业务数据不会说谎。