news 2026/10/20 6:01:02

向量数据库选型与调优:Milvus、Qdrant、pgvector全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
向量数据库选型与调优:Milvus、Qdrant、pgvector全解析

有这两年在做AI应用或基础架构的朋友,估计都躲不开一个词:向量数据库。大模型RAG、语义检索、以图搜图这些场景铺开之后,传统数据库那套精确匹配的玩法在模糊语义查询面前显得特别无力,于是专门为高维向量做存储和检索的数据库就成了标配。这篇文章我打算把市面上常用的向量数据库一次性讲透,不仅告诉你它们各自是什么,更会结合我实际跑过的场景,聊聊选型思路、部署要点、参数调优和一些踩坑记录。不管你是刚接触这个概念想挑一个上手,还是已经在用某个产品打算横向对比,这篇应该都能给你省下不少时间。

1. 向量数据库到底解决什么问题

1.1 传统数据库做不到的模糊语义匹配

先说清楚一个事情:为什么传统的关系型数据库做不了这个事。SQL里我们查一条记录,靠的是精确条件,比如where name = '张三',或者范围条件,比如where age > 18。这种匹配逻辑建立在“值相等”的基础上。但当你问“找一张跟这张图片风格类似的图”或者“找一段语义上差不多的话”时,问题就变成了“相似”,而不是“相等”。

相似度怎么衡量?目前工业界最通用的做法是:先把图片、文本、音频这些非结构化数据用模型转成一个固定维度的浮点数数组,业内管这个叫“嵌入向量”或“Embedding”。比如一段话通过模型处理后变成128维或1024维的向量,语义接近的内容在高维空间里的距离就越近。向量数据库干的事情就是把这些向量存起来,然后在你给一个查询向量时,以最快的速度返回“距离最近”的一批向量及对应的原始数据。

这里面有个核心矛盾:高维空间里暴力计算距离是可以的,但如果数据量上万、上百万甚至上亿,暴力比对的计算量会大到完全不可接受。向量数据库的价值正是用各种索引结构和近似检索算法,把“大海捞针”变成“沿着索引路径快速定位”,这也是它和传统数据库最根本的分水岭。

1.2 必须搞懂的三个基础概念

理解向量数据库之前,有三个概念绕不开,分别是嵌入向量、距离度量和最近邻检索。

嵌入向量听起来高大上,其实可以理解成“对原始内容做了一次特征压缩”。一张猫的图片、一段关于猫的文字,如果来自同一套模型体系,它们在语义上应该被映射到高维空间中比较接近的位置。所以嵌入向量的质量直接决定了后续检索效果的上限,这个认知非常重要——我见过很多团队在检索效果不好时拼命调数据库参数,最后发现是模型选错了,白费功夫。

距离度量则是定义“什么叫近”。最常用的有欧氏距离、余弦相似度和点积。欧氏距离直接计算空间中的直线距离,适合低维稠密向量;余弦相似度看的是方向一致程度,对向量模长不敏感,文本语义场景用得最多;点积更适合带归一化处理的场景。选哪个度量标准,一定要和生成Embedding时使用的模型配套,否则检索出来的结果会完全乱掉。

最近邻检索(kNN)就是找“最近的K个邻居”的算法问题。精确kNN在数据量小时没问题,数据量大时得靠近似最近邻(ANN)来提速。向量数据库的核心技术栈,基本就是围绕ANN索引在演进,比如HNSW图算法、IVF倒排思想、PQ乘积量化这些。理解这些索引类型,是后续调优的前提。

1.3 向量检索的大致流程

一个完整的向量检索流程,可以拆成四步:

  1. 数据预处理:把原始内容通过Embedding模型转成向量。
  2. 写入存储:把向量和原始数据(或源内容ID)一起写入向量数据库,同时建立索引。
  3. 查询下发:用户输入一个查询,同样转成向量,带上希望返回的条数K。
  4. 近似检索:数据库在索引结构里快速找到K个最近邻,返回结果。

这个流程看起来不复杂,但实际工程里要处理的问题特别多:写入性能和数据源打通、索引构建的内存开销、数据更新和删除的一致性、版本控管、以及把向量搜索和标量过滤组合在一起的条件检索。不同数据库在这些环节上做了不同取舍,下面逐个拆解。

2. 主流向量数据库的分类与定位

2.1 三类形态:专业库、扩展插件、托管服务

市面上叫“向量数据库”的东西其实形态差异巨大,先有个分类意识会帮助你快速判断。第一类是完整的独立数据库系统,典型代表有Milvus、Qdrant、Weaviate、Chroma,它们自研或深度定制了存储引擎和索引模块,根据自己的场景来决定部署和运维方案。第二类是寄生在已有数据库上的扩展,最典型的是pgvector,它利用PostgreSQL本身的能力加上向量字段和索引支持,不用额外部署一套新服务。第三类是全托管的SaaS服务,比如Pinecone这类云产品,开箱即用、免运维,但对网络环境和成本有更高要求。

这三类没有绝对的优劣,更多是取舍。独立数据库功能全面、性能上限高,但意味着要运维一套新的基础设施;扩展插件部署简单、能复用现有技术栈,但性能和高级功能可能受限;托管服务最省事,不过长期使用费用可观,而且数据要出企业内网,合规敏感的项目需要谨慎。

2.2 几个常用产品的定位速览

我按使用场景给大家分一下层:

Milvus定位是“大规模生产级检索平台”。数据量到千万级以上、查询并发高、需要水平扩展、又要跟模型和业务做复杂集成时,Milvus基本是首选。它架构上把数据插入、索引构建、查询和存储分离,支持分布式部署,适用于广告推荐、问答引擎、图片检索这类重场景。

Chroma则是“开发者的轻量玩具到工具链桥梁”。安装只需要一个pip命令,本地起服务大概不要五分钟,适合快速做原型验证、搭RAG Demo、写教学项目。数据量在百万级以下时Chroma的体验非常愉悦,但如果想扛高并发和亿级数据,它就不太合适了。

Qdrant走的是“性能极致”路线。Rust语言实现,单个节点就能处理相当大的数据量,尤其它的Payload过滤机制做得很精致,能在向量检索的同时高效完成标量条件过滤,这对电商场景“根据品类+价格过滤再按语义排序”之类的需求非常友好。

pgvector最大的优势是“没有引入新东西”。如果公司已经有PostgreSQL了,加一个插件就能获得向量能力,SQL生态完全复用,对DBA来说几乎零学习成本。适合数据量中等、不想为了向量检索专门维护一套集群的团队。

Weaviate在对象存储和“混合检索”上做了很多功夫,比如GraphQL接口,对多模态和知识图谱结合的场景有一定优势。但它的社区规模和生态相比前几个稍弱,国内很多团队用的不多。

2.3 一张表看懂大致的选型方向

如果现在还不确定选哪个,可以先从下面这张表快速建立概念:

数据库部署形态适合数据规模核心优势主要局限
Milvus独立服务/分布式千万级以上扩展性强、功能全、生态较完善部署运维成本较高
Qdrant独立服务/单机-集群百万到亿级性能高、过滤机制强大集群生态相对年轻
Chroma嵌入式/本地服务百万级以下上手极快、文档友好生产级能力偏弱
pgvectorPostgres插件百万到千万级复用现有SQL和运维高级索引与扩展性有限
Weaviate独立服务百万到千万级混合检索、GraphQL社区与生态相对小

这个表只能代表大部分常规场景,真实选型还要考虑查询模式、写入压力、团队背景等。后面我会单独拿一节讲选型背后的深度考量。

3. 核心产品实操拆解

这一部分我把用得最多的几个产品逐个演示一遍,包括安装、初始化、建集合、写入和检索。代码不会贴特别多,但关键调用方式都会给到,你照着跑一遍就能对每个产品形成直观感受。

3.1 Milvus:分布式架构扛大规模检索

Milvus上手的第一感觉是“重但完整”。2.x版本以后底层存储用对象存储,计算层和存储层可以独立扩缩容,生产环境一般用Docker Compose或者Kubernetes部署。开发环境最省事的做法是直接用Docker跑Standalone模式:

docker run -d --name milvus \ -p 19530:19530 \ -p 9091:9091 \ milvusdb/milvus:latest standalone

启动需要组件齐全,实际项目里我推荐直接用官方提供的milvus-standalone-docker-compose.yml,一次性连同etcd和MinIO一起拉起来。

Python环境安装pymilvus客户端后,建集合和写入数据是这样:

from pymilvus import connections, CollectionSchema, FieldSchema, DataType, Collection connections.connect(host="localhost", port="19530") fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=False), FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=768), # 维度要和模型输出对齐 FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=64), ] schema = CollectionSchema(fields=fields, description="demo collection") collection = Collection(name="image_search", schema=schema) collection.create_index("embedding", {"index_type": "HNSW", "metric_type": "COSINE", "params": {"M": 16, "efConstruction": 200}})

插入时直接传入向量列表和标量字段:

import numpy as np dim = 768 vectors = np.random.random([1000, dim]).tolist() data = [ list(range(1000)), vectors, [f"cat_{i}" for i in range(1000)], ] collection.insert(data) collection.flush()

检索时,如果直接collection.search()会拿到结果,但如果数据还没建好索引,Milvus会自动用暴力检索兜底,数据量大时响应会明显变慢。所以务必在插入足够数据后再手动create_index。

collection.load() res = collection.search( data=[vectors[0]], anns_field="embedding", param={"metric_type": "COSINE", "params": {"ef": 64}}, limit=10, output_fields=["id", "category"], )

Milvus的索引构建是异步的,这意味着刚插入的数据不一定立刻能查到,必须等索引构建完成。这个特性在生产上会带来一些数据可见性延迟,设计业务时要对“写入后立刻检索返回”有预期管理。

3.2 Chroma:零运维起步的原型利器

Chroma我愿称之为“矢量数据库界的一匹黑马”。它的目标就是给AI应用开发者一个最简单的持久化方案,甚至不需要启动服务,直接用本地模式跑起来:

pip install chromadb

写一个完整的RAG底座的代码量少得感人:

import chromadb client = chromadb.PersistentClient(path="./chroma_data") # 持久化到本地目录 collection = client.get_or_create_collection("docs") collection.add( documents=["深度学习入门指南", "向量数据库实践手册"], embeddings=None, # 如果不传embedding,客户端会调用默认模型 ids=["doc_1", "doc_2"], metadatas=[{"source": "tutorial"}, {"source": "manual"}], ) results = collection.query( query_texts=["机器学习怎么学"], n_results=2, )

这里有个容易搞混的点:embeddings=None时,Chroma会尝试加载默认的Embedding模型来做文本转向量,但这个方法依赖网络下载模型,在受限网络环境下会遇到各种问题。我的习惯是自己在外部统一生成向量再传入,这样文本处理流程的可控性更高。

Chroma也支持本地服务模式,在命令行执行chroma run就能起HTTP服务,端口默认8000。不过从我的实测看,它的HTTP接口能力比Python内嵌模式弱一些,所以如果项目是Python技术栈,直接用PersistentClient更舒服。

3.3 Qdrant:用Rust换来的过滤性能

Qdrant给我的感觉就是“精致”。它把向量和payload(标量元数据)放在同一个存储引擎里管理,检索时能在遍历候选集的过程中同步应用过滤条件,这比“先全量向量检索再过滤标量字段”的做法高效得多。

本地启动Qdrant非常轻量:

docker run -d --name qdrant \ -p 6333:6333 \ qdrant/qdrant

创建集合要指定向量尺寸和距离函数,然后就可以往里面写数据:

from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct client = QdrantClient(host="localhost", port=6333) client.create_collection( collection_name="products", vectors_config=VectorParams(size=512, distance=Distance.COSINE), ) points = [ PointStruct( id=i, vector=[0.1] * 512, # 实际项目中用模型生成的向量 payload={"category": "electronics", "price": i * 10}, ) for i in range(100) ] client.upsert(collection_name="products", points=points)

检索时把过滤条件直接放进请求里,这一步是Qdrant拿手戏:

hits = client.query_points( collection_name="products", query=[0.1] * 512, # 支持传原始向量 query_filter={"must": [{"key": "category", "match": {"value": "electronics"}}, {"key": "price", "range": {"gte": 100}}]}, limit=10, )

如果查询时只传文本不传向量,Qdrant也能配合FastEmbed等工具做内嵌向量化,不过工程上更推荐“查询向量提前算好,请求里只带向量”,这样延迟更可控。

Qdrant的另一个优点是单机性能确实好,Rust写出来的底层没有GC停顿,在高并发下有稳定的尾延迟表现。这在推荐系统这类对延迟敏感的场景里很重要。

3.4 pgvector:让PostgreSQL顺手拥有向量能力

如果公司现有的数据基础设施已经是PostgreSQL,那pgvector可能是你“性价比”最高的选择。它不需要引入新服务,只需要在数据库里启用扩展:

CREATE EXTENSION IF NOT EXISTS vector;

建表时指定向量列,维度固定。支持降维不需要的数据用注释标记即可:

CREATE TABLE items ( id bigserial PRIMARY KEY, content text, embedding vector(768) );

插入数据跟普通SQL没有区别,只是向量列需要使用字符串格式的数组:

INSERT INTO items (content, embedding) VALUES ('向量数据库实践', '[0.1,0.2,0.3,...]'::vector);

创建HNSW索引:

CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);

查询K个最近邻:

SELECT content, 1 - (embedding <=> '[0.1,0.2,...]'::vector) AS similarity FROM items ORDER BY embedding <=> '[0.1,0.2,...]'::vector LIMIT 10;

这里<=>是余弦距离算符,也可以用<->做欧氏距离,用<#>做负内积。索引类型和算子必须对应上,否则查询会无法走索引。

pgvector的局限主要体现在两个地方:一是它的索引构建和更新与PostgreSQL自身的VACUUM、WAL机制耦合在一起,大数据量下索引构建会比较慢;二是水平扩展能力依赖PostgreSQL自身的分库分表方案,不像Milvus那样原生支持分布式。所以数据量在两千万以内时它很香,超过这个量级就要认真评估了。

4. 选型背后的关键考量与参数调优

4.1 从数据规模和查询量反推方案

很多人在选型时问“哪个最好”,但正确答案是“哪个最适合你的场景”。我做选型评估时,会先把下面几个数字拉出来量化:

  • 向量总量级:多少条记录要存储?
  • 单条向量维度:通常是256、512、768还是1024?
  • QPS预期:峰值查询是多少,对延迟的要求是多少?
  • 写入频率:是离线批量灌入还是实时持续写入?
  • 过滤需求:查询时要不要配合标签、类目、价格范围等标量条件?

这几个参数定出来后,方案的轮廓就清晰了。比如数据量只有十万条、查询量一天几千次,那就没必要上一套分布式Milvus集群,Chroma或pgvector就能轻松满足。如果数据量到了千万级、查询QPS上千、还需要复杂过滤逻辑,那Qdrant或Milvus才值得考虑。

一个容易被忽略的坑是向量维度。很多人做POC时用128维验证,效果不错,一到生产换成了768维,内存开销瞬间变成6倍。向量维度直接影响索引占用的内存和查询延迟,选Embedding模型时就要把“上线后的存储成本”纳入评估。

4.2 索引类型选择:HNSW、IVF适用的场景差异

几乎所有向量数据库都支持多种索引类型,最常用的是HNSW和IVF。理解这两者的差异,会让你在调参时更有方向。

HNSW是一种基于多层图的近似最近邻算法。它的核心思想是维护一个分层的图结构,在查询时从顶层开始,逐层向下搜索,找到离查询点最近的节点后再进入下一层精细搜索。HNSW的召回率高、查询速度快,但索引构建占用内存较大,而且对写入速度有一定影响。适用于数据规模中等、查询性能要求高、对内存不太敏感的场景。

IVF则借鉴了传统搜索引擎的倒排索引思路。它先把向量空间聚类成很多桶(比如nlist=1024个簇),查询时只搜索距离查询点最近的几个簇(比如nprobe=8),从而大幅减少计算量。IVF的内存占用比HNSW低,构建速度也快,但在同等召回率下查询延迟通常不如HNSW。

从我跑过的基准测试来看,如果内存不是瓶颈,大多数场景无脑选HNSW都能获得更好的体验。IVF更适合超大规模数据、内存受限或者对召回率要求不那么极致的场景。

还有一个常见优化是PQ(乘积量化)。它把向量切分成若干子空间,每个子空间做量化压缩,能显著减少内存占用,但会带来一定的精度损失。数据量极大、内存吃紧时可以上PQ,但建议先在部分数据上做精度测试,确认召回率达到业务底线再投入使用。

4.3 参数调优的关键指标

HNSW索引里有几个参数直接影响性能,分别是M、efConstruction和ef。

M表示每个节点最多连接的邻居数量。M越大,图越稠密,召回率越高,但内存和构建时间都会上升。常见取值在16到64之间,我一般从16起调,看召回率不够再往上加。

efConstruction控制索引构建时的搜索宽度。它越大,构建越耗时,但图质量越好。建议设置在100到200之间,默认值偏低的话,可以手动调高。

ef是查询时的搜索宽度。它控制搜索过程中的候选集规模,ef越大召回率越高,但查询越慢。这个参数可以动态调整,业务对延迟敏感就调小,对召回敏感就调大。

这几个参数的关系,类比一下就是:M决定了路网的密度,efConstruction决定了修路时多仔细,ef决定了导航时愿意探索多少条岔路。很多文章只给结论不给直觉,这里我把背后的取舍逻辑讲透,后续不管换哪个产品你都能举一反三。

另外还要特别提一下“归一化”。很多向量数据库在构建索引时会默认对你传入的向量做归一化,如果你的业务里又手动归一化了一遍,距离计算会受影响。不同产品对归一化的处理不同,比如Qdrant默认不做归一化,需要在代码里显式处理。这种细节在文档里往往不明显,但一旦匹配模式有问题,召回结果会非常诡异。

5. 常见问题与排查技巧实录

5.1 维度不匹配引发的检索失败

这是我见过最多的新手失误:建集合时定义了768维,加载模型时用了个输出512维的Embedding接口,写入和查询时直接报错或者干脆静默返回空结果。

排查思路其实很简单:任何报错信息先看维度相关字段,确认集合配置和输入向量的shape是不是一致。更稳的做法是写一个小脚本,统一管理模型和数据库的向量维度配置,并把维度及时同步到schema里,避免分开手工配置造成漂移。

5.2 内存与磁盘资源失控

向量索引吃内存的速度经常超出预期。举个例子:100万条768维的向量,每条768个float32就是约3KB,裸数据就要3GB内存,HNSW索引算上图的邻接关系后通常会扩大到裸数据的1.5到2.5倍。如果有过滤器或存了大量原文,内存占用会更夸张。

调优思路有三种:压缩向量精度,比如float16代替float32;降低维度,比如做PCA降维;启用量化索引(PQ或Scalar Quantization)。此外要建好监控,分片数、索引内存、查询峰值都配上告警,防止服务在流量高峰期被拖垮。

5.3 召回率和精度不达标

如果返回的结果明显不符合预期,先说一句:大多数时候问题不在数据库,而在Embedding模型。两个文本语义相似,如果模型表达力不足,再好的检索索引也救不回来。

排查时可以做一个简单实验:手动挑几条测试数据,计算它们的向量相似度,看看模型本身的语义距离是否符合直觉。如果模型侧没问题,再去看距离算法和索引参数。召回率低通常是ef设太小,或者归一化处理出了问题。这种“先质疑模型,再折腾数据库”的排查顺序能节省大量时间。

5.4 过滤条件拖垮性能

在向量检索的同时加上大量标量过滤条件,性能下降是一定的,因为数据库需要先粗筛候选集,再做向量距离计算。不同数据库对过滤的处理策略不同,Qdrant的过滤能力做得好,Milvus较新版本也有“先过滤后检索”的优化,但Chroma在过滤场景下性能就弱一些。

如果你的业务查询经常带过滤条件,就要在选型阶段就把这个因素考虑进去。另一个优化思路是反向倒置:在写入时就把数据按某个热门维度拆成多个集合,查询时直接定向到相应集合,绕开过滤。

5.5 删除数据后空间不释放

向量数据库的删除操作在很多产品里是软删除,实际存储空间不会马上回收。比如Milvus的delete本质是给记录打标记,空间回收依赖后续的compaction。如果频繁删除插入导致性能越来越差,就该主动触发合并或重建索引。

这个点很容易被忽视,因为常规数据库的delete语义是即刻的。在生产项目里我吃过这个亏:大量数据更新后,明明文档说已经删除,但查询总能拉到旧数据,后来才发现是删除的可见性和compaction周期问题。建议在数据写入量大但删除少的场景使用“分区重建”策略,而不是频繁单条删除。

最后再分享一点个人的实操心得

我实际用过Milvus、Chroma、Qdrant和pgvector,最大的感受是:没有任何一个产品可以通吃所有场景,关键是先想清楚自己的数据规模、查询模式、运维能力,再决定选型。如果你正在做POC,我的建议是别一开始就切到分布式方案,先用Chroma把完整流程跑通、评估好模型效果,再上生产级的Qdrant或Milvus。这个过渡路径最平滑,也能让团队在早期避免被运维复杂度拖住节奏。另外,不管选哪个产品,建议在架构设计阶段就把向量维度、索引参数、监控告警这些事拉通定义好,这些前期决策在数据量上来之后想改,代价往往是推倒重来。做技术选型,最怕的不是选错,而是没想清楚为什么选。希望这篇文章能帮你在“为什么”这件事上省点力气。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 6:00:11

深入理解context-mode:三种场景下的上下文管理实战与避坑指南

接手一个遗留系统&#xff0c;最怕的就是那种“改一处、崩三处”的状态管理代码。上个月我帮某个团队排查一个诡异的登录态错乱问题&#xff0c;断断续续找了一天&#xff0c;最后发现根子就出在一个被到处引用的全局对象上——它存着上个请求的用户上下文&#xff0c;结果下个…

作者头像 李华
网站建设 2026/10/20 6:00:09

从僵硬到流畅:机器人全身运控方案长板与短板全解析

你看到过机器人跳舞的视频吗&#xff1f;反正我是看过不少&#xff0c;第一次看的时候觉得挺新奇&#xff0c;后来见得多了&#xff0c;就越看越觉得不对劲——很多机器人跳舞&#xff0c;动作确实做出来了&#xff0c;但总透着一股“僵硬感”&#xff0c;像是一台精密机床在强…

作者头像 李华
网站建设 2026/10/20 5:59:29

nps服务端API安全加固:AES加密、HMAC签名与RBAC权限控制实战

1. 为什么nps服务端的API需要认真加固1.1 一个看起来很常见的裸奔现场前阵子帮一个团队加固他们 nps 服务端 API 的时候&#xff0c;我发现一个很典型的问题&#xff1a;接口本身没毛病&#xff0c;但整个调用链路一直在裸奔。说的准确点&#xff0c;API 有 token&#xff0c;但…

作者头像 李华
网站建设 2026/10/20 5:58:51

中望3D2026测量功能详解:建模、装配与出图的可靠尺寸验证

如果你经常用中望3D建模&#xff0c;应该会有这样的体会&#xff1a;模型在屏幕里转来转去&#xff0c;看着挺像回事&#xff0c;可真到要出图、做装配或者交接给下游的时候&#xff0c;心里总有点虚——这里的间隙到底够不够&#xff1f;这个斜面角度是不是我当初想的值&#…

作者头像 李华
网站建设 2026/10/20 5:58:51

LeRobot与ROS2:具身智能时代机器人的“大脑”与“小脑”分工解析

在技术社区里&#xff0c;我经常看到一类提问&#xff1a;“LeRobot能不能用来做机器人的路径规划&#xff1f;能不能直接当ROS2的下位替代&#xff1f;”每次看到这种问题&#xff0c;都感觉问的人和答的人不在一个频道上。LeRobot确实挂着“robot”的名号&#xff0c;而且近年…

作者头像 李华