0. 阅读导引 ✨ 嘿~这是「AI 基础设施三部曲」的第二篇(第一篇是隔壁的 RAG 完全指南)!
如果你曾经有过这些疑问——
“向量数据库和 MySQL 有什么区别?为什么不能直接用传统数据库存向量?” 🤔
“HNSW、IVF、PQ…这几个大写字母到底是干嘛的?” 📖
“ChromaDB、Milvus、Qdrant、Weaviate、Pinecone…这么多到底选哪个?” 😵💫
——那这篇文章就是为你准备的!
读完你会获得什么?
从数学原理到工业实现的完整向量数据库认知
能独立做向量数据库的技术选型
理解 ANN 索引算法的核心思想(无需深厚的数学背景)
知道如何优化向量检索的 QPS 和召回率
本文约 1.8 万字,配合咖啡食用更佳 ☕
1. 先搞懂”向量”到底是个啥 📐 1.1 从一句话到一个数组 想象你要向一个从没见过水果的外星人描述”苹果”和”香蕉” 👽
你可能会建立一个特征表:
甜度
硬度
颜色偏红
颜色偏黄
形状圆
形状长
🍎 苹果
0.7
0.8
0.9
0.1
0.9
0.1
🍌 香蕉
0.8
0.3
0.1
0.9
0.2
0.9
把苹果的特征提取出来:[0.7, 0.8, 0.9, 0.1, 0.9, 0.1]
这个数组就是苹果的 向量(Vector) 。6 个数字 = 6 维。
真实世界的 Embedding 模型不是手工设计特征,而是通过神经网络自动学习——但核心理念完全一样:把语义相似的文本映射到空间中相近的位置 。
1 2 3 4 5 6 7 8 9 10 高维空间中的距离示意图(画成 2D): 🍎(0.7, 0.8) │ ← 距离很近(都是水果) 🍌(0.6, 0.7) │ │ ← 距离很远! │ 🚗(0.1, -0.3) ---- 🏍️(-0.1, -0.2) ← 距离很近(都是交通工具)
1.2 向量的三个核心操作 在向量数据库的世界里,你只需要理解三种操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ① 存(Insert) 文档 → Embedding 模型 → 向量 → 存入数据库 "今天天气真好" → [0.23, -0.45, 0.89, ..., 0.12] ↓ 向量数据库(持久化存储) ② 查(Search) 用户问题 → Embedding 模型 → 查询向量 "外面天气怎么样?" → [0.21, -0.43, 0.91, ..., 0.15] ↓ 在库里找到最相似的 K 个向量 → 返回原始文本 ③ 相似度计算 query_vec · doc_vec → 相似度分数(越大越像) 或 ||query_vec - doc_vec|| → 距离(越小越像)
1.3 三种相似度度量——怎么计算”像不像” 📏 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import numpy as npa = np.array([0.7 , 0.8 , 0.9 ]) b = np.array([0.6 , 0.7 , 0.8 ]) euclidean = np.linalg.norm(a - b) print (f"欧氏距离: {euclidean:.4 f} " ) cosine = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) print (f"余弦相似度: {cosine:.4 f} " ) dot = np.dot(a, b) print (f"内积: {dot:.4 f} " )
选哪个?
场景
推荐度量
原因
文本 Embedding(已 L2 归一化)
余弦 / 内积
归一化后两者等价
图片 Embedding(未归一化)
余弦
不受亮度/对比度影响
推荐系统
内积
与矩阵分解的评分预测一致
地理坐标/传感器数据
欧氏
物理距离有实际意义
⚠️ 关键提醒 :如果你用的 Embedding 模型已经做了 L2 归一化(normalize_embeddings=True),余弦相似度和内积是完全等价的!大多数现代 Embedding 模型默认归一化。
2. 为什么不能直接用 MySQL?——向量检索的特殊性 🔍 2.1 传统数据库的”查找”,和向量检索的”查找” 1 2 3 4 5 6 7 SELECT * FROM products WHERE price BETWEEN 100 AND 200 ;
2.2 暴力检索的成本 假设你有 100 万个 1024 维的 32-bit 向量:
1 2 3 4 存储:100万 × 1024 × 4 bytes = 约 4 GB(纯向量,不算元数据) 一次查询的计算量:100万 × 1024 次浮点运算 = 约 10 亿次 FLOPS 单次查询耗时(单线程):~100ms(仅计算时间,不算 I/O) 100 QPS 的服务器:需要 10 亿次/秒 的吞吐...
100 万数据就这样了,如果 1 亿呢?100 亿呢?
这就是向量数据库存在的根本理由——你不能每次都做全量扫描。
2.3 近似最近邻(ANN)——“差不多就行了”的艺术 🎨 关键洞察:在 Embedding 空间里,大多数向量都和你的查询不相关。
既然如此,为什么要把时间浪费在那些”无关”的向量上呢?
1 2 3 4 精确 KNN: 近似 ANN: 比较 100万 个向量 只比较 ~3000 个向量 100% 的 Top-K 是正确答案 95-99% 的 Top-K 是正确答案 耗时 100ms 耗时 1-3ms
这就是 ANN 的哲学:用一点点精度换回几十倍的性能。
3. 向量索引算法——数据库的心脏 🫀 这是本文最”硬核”的一节,我会尽量用图解和类比来解释,零数学恐惧症友好 (๑•̀ㅂ•́)و✧
3.1 哈希类方法(LSH)——分桶大法 🪣 核心理念 :设计一种”有语义感知”的哈希函数,让相似的向量有更大的概率被分到同一个桶里。
1 2 3 4 传统哈希: LSH 哈希: "apple" → 桶 #42 向量 A [0.7, 0.8, ...] → 桶 #3 "applf" → 桶 #999 向量 B [0.6, 0.7, ...] → 桶 #3 ← 同一个桶! 向量 C [-0.5, 0.3, ...] → 桶 #7 ← 不同桶
构建时:用多个 LSH 函数(如 10 个),每个向量存在 10 个桶里 查询时:计算查询向量的 10 个桶号,只搜索这些桶里的向量
1 2 3 优点:构建快、理论保证好 缺点:精度偏低(通常 70-85%)、内存消耗大(多副本) 现状:已被新兴方法(HNSW/IVF)取代,主要用于理论研究和特定场景
3.2 聚类类方法(IVF)——先找”区”,再找”人” 🏘️ 核心思想 :把 100 万个向量分成 1000 个”小区”(聚类),查询时先定位到最近的几个小区,只在这些小区里搜。
%%{init: {'theme': 'base'}}%%
graph LR
A[全量向量 100万] --> B{K-Means 聚类}
B --> C[聚类 1: 1000 向量]
B --> D[聚类 2: 1200 向量]
B --> E[...]
B --> F[聚类 1000: 800 向量]
Q[查询向量] --> S{找最近的 3 个聚类中心}
S --> C
S --> D
S --> F
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from sklearn.cluster import KMeansimport numpy as npclass SimpleIVF : def __init__ (self, n_clusters=1000 , n_probe=10 ): self .n_clusters = n_clusters self .n_probe = n_probe self .kmeans = KMeans(n_clusters=n_clusters) self .vectors = None self .cluster_assignments = None def build (self, vectors ): """构建索引""" self .vectors = vectors self .cluster_assignments = self .kmeans.fit_predict(vectors) self .clusters = {i: [] for i in range (self .n_clusters)} for idx, cluster_id in enumerate (self .cluster_assignments): self .clusters[cluster_id].append(idx) def search (self, query_vec, k=10 ): """搜索 Top-K""" centers = self .kmeans.cluster_centers_ distances = np.linalg.norm(centers - query_vec, axis=1 ) nearest_clusters = np.argsort(distances)[:self .n_probe] candidates = [] for cid in nearest_clusters: for idx in self .clusters[cid]: dist = np.linalg.norm(self .vectors[idx] - query_vec) candidates.append((idx, dist)) candidates.sort(key=lambda x: x[1 ]) return candidates[:k] ivf = SimpleIVF(n_clusters=1000 , n_probe=10 ) ivf.build(my_vectors) results = ivf.search(query, k=10 )
IVF 关键参数 :
n_clusters(聚类数):通常设为 sqrt(N) 到 4*sqrt(N)。100 万向量 → 1000-4000 个聚类
n_probe(探测聚类数):越大精度越高但越慢。常用 8-32
3.3 图类方法(HNSW)——当前工业界的王者 👑 HNSW(Hierarchical Navigable Small World)是目前最主流的向量索引算法 。几乎所有现代向量数据库都把 HNSW 作为默认/推荐索引。
3.3.1 用”六度分隔”来理解 你可能听说过”六度分隔理论”——世界上任意两个人之间,最多通过 6 个中间人就能认识。
HNSW 做的就是这个:它构建一个朋友关系网(图) ,每个向量是图中的节点,相似的向量之间连一条边。
1 2 3 4 5 6 7 8 9 10 没有 HNSW 时: 从起点 → 遍历所有点 → 找到最近的点 (太慢了!) 有了 HNSW: 起点 "狗.jpg" → 朋友 "狼.jpg"(有点像) → 朋友的朋友 "狐狸.jpg"(更像了!) → 朋友的朋友的朋友 "郊狼.jpg"(找到了!) (只跳了 3 步)
3.3.2 为什么是”分层的”(Hierarchical)? 1 2 3 4 5 6 7 8 9 10 11 第 2 层(最稀疏): ───── ● ───── ● ───── / \ 第 1 层(中等): ─ ● ─ ─ ● ─ ─ ● ─ ● ─ ─ / \ / \ 第 0 层(全部节点):● ─ ● ─ ● ─ ● ─ ● ─ ● (所有向量都在这一层) 搜索策略: ① 从第 2 层开始,做"大跨步"(少数节点,跳得快) ② 到第 1 层,做"中跨步" ③ 到第 0 层,做"小碎步"精细搜索
这就像你先看世界地图定位国家,再看国家地图定位城市,最后看城市地图精确找到街道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 HNSW_CONFIG = { "M" : 16 , "ef_construction" : 200 , "ef_search" : 50 , }
3.3.3 HNSW 的优缺点 1 2 3 4 5 6 7 8 9 10 ✅ 优点: · 查询速度极快(1-3ms @ 百万级) · 召回率高(95-99%) · 增量插入友好(不需要重建) · 实现成熟,几乎每个向量数据库都支持 ❌ 缺点: · 内存占用大(需要存图结构和全量向量) · 构建时间较长(特别是 ef_construction 设得大时) · 删除操作相对复杂
3.4 量化类方法(PQ)——“压缩饼干”技术 🍪 当数据量达到亿级 时,即使是 HNSW 也可能需要几百 GB 内存。这时候你需要 **Product Quantization(PQ)**来压缩向量。
3.4.1 PQ 的核心思想 1 2 3 4 5 6 7 8 9 10 原始向量(1024 维 × 4 字节 = 4KB/条): [0.23, -0.45, 0.89, 0.12, -0.67, ..., 0.34] PQ 压缩后: ① 把 1024 维切成 64 段,每段 16 维 ② 对每段做 K-Means 聚类(比如 256 个中心) ③ 每段只需要存"聚类中心编号"(0-255,只需 1 字节) ④ 64 段 × 1 字节 = 64 字节/条! 4KB → 64 字节,压缩了 64 倍!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 from sklearn.cluster import KMeansimport numpy as npclass SimplePQ : def __init__ (self, n_subvectors=64 , n_centroids=256 ): self .n_subvectors = n_subvectors self .n_centroids = n_centroids self .codebooks = [] def train (self, vectors ): """训练 64 个 K-Means 模型""" dim = vectors.shape[1 ] sub_dim = dim // self .n_subvectors for i in range (self .n_subvectors): start = i * sub_dim end = (i + 1 ) * sub_dim sub_vectors = vectors[:, start:end] kmeans = KMeans(n_clusters=self .n_centroids) kmeans.fit(sub_vectors) self .codebooks.append(kmeans) def compress (self, vector ): """把一个 1024 维向量压缩成 64 个编码""" dim = len (vector) sub_dim = dim // self .n_subvectors codes = [] for i in range (self .n_subvectors): start = i * sub_dim end = (i + 1 ) * sub_dim sub = vector[start:end].reshape(1 , -1 ) code = self .codebooks[i].predict(sub)[0 ] codes.append(code) return np.array(codes, dtype=np.uint8)
3.4.2 PQ 的实际使用 PQ 通常与 IVF 组合使用(IVF+PQ ),这是处理亿级以上 数据的标准方案:
IVF 负责”粗筛”(缩小搜索范围)
PQ 负责让所有向量都能”装进内存”
1 2 3 场景:10 亿个 1024 维向量 原始:10亿 × 4KB = 4 TB(装不进内存) IVF+PQ 后:10亿 × 64 字节 = 64 GB(可以装进内存!)
3.5 算法全景对比 🗺️
算法
内存占用
查询速度
召回率
增量插入
百万级
亿级
暴力 KNN
最低
🐌 极慢
🎯 100%
✅
❌
❌
LSH
中
🐇 快
⭐⭐ 一般
✅
✅
✅
IVF
低-中
🐇 快
⭐⭐⭐ 好
❌ 需重建
✅
✅
HNSW
高
🚀 最快
⭐⭐⭐⭐ 极好
✅
✅
❌ 内存
IVF+PQ
极低
🐇 快
⭐⭐⭐ 好
❌
✅
✅
DiskANN
低(SSD)
🚀 快
⭐⭐⭐⭐ 极好
✅
✅
✅
3.6 DiskANN——2025 年最值得关注的新方向 💾 2023 年微软开源的 DiskANN 解决了一个核心矛盾:精度高的(HNSW)吃内存,省内存的(IVF+PQ)精度低 。
DiskANN 的核心思路:把图索引放在 SSD(固态硬盘) 上,查询时只把需要的节点加载到内存。
1 2 3 4 5 6 7 8 传统 HNSW:100% 在内存 → 内存瓶颈 DiskANN: 图结构在 SSD → 查询时按需读取 → 突破内存限制 性能实测(微软论文): 10 亿个 100 维向量 HNSW:需要 1.2 TB 内存(太贵了) DiskANN:只需 64 GB RAM + 1TB SSD 查询延迟:< 3ms,召回率 > 95%
2025 年在哪些数据库里可用?
LanceDB:内置 DiskANN 支持(LANCEDB_BUILDER=diskann)
Qdrant:2025.04 开始支持 mmap(内存映射)模式,类似思路
Milvus:MMap 模式支持
4. 主流向量数据库深度对比 🔬 好,理论准备足了。现在来逐一解剖每一个主流向量数据库。我会从架构、性能、适用场景、坑点四个维度来说。
4.1 Milvus——企业级航母 🚢 1 2 3 4 架构:云原生、存算分离 语言:Go(核心)+ C++(索引) 许可证:Apache 2.0 GitHub Stars:~32k
架构 :
%%{init: {'theme': 'base'}}%%
flowchart TB
subgraph 计算层
Q["🔍 Query Node<br/>(查询)"]
D["📊 Data Node<br/>(数据管理)"]
I["⚙️ Index Node<br/>(索引构建)"]
end
Q --> P
D --> P
I --> P[("📨 消息队列<br/>(Pulsar)")]
P --> M1[("🗄️ MinIO<br/>对象存储")]
P --> M2[("🔧 etcd<br/>元数据")]
P --> M3[("📋 MySQL<br/>元数据")]
索引支持 :HNSW, IVF_FLAT, IVF_PQ, IVF_SQ8, DISKANN, GPU_IVF_FLAT, GPU_IVF_PQ
适合场景 :
✅ 数据量 1000 万~100 亿
✅ 需要分布式、高可用
✅ 公司有专门的运维团队
✅ 混合查询(标量过滤 + 向量检索)
不适合 :
❌ 个人项目/原型验证(太重了)
❌ 只有几千条数据的小场景(杀鸡用牛刀)
❌ 需要在客户端(浏览器/手机)运行
一句话总结 :向量数据库界的 Kubernetes——功能强大,但运维复杂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 from pymilvus import MilvusClient, DataTypeclient = MilvusClient("http://localhost:19530" ) schema = client.create_schema( auto_id=False , enable_dynamic_field=True ) schema.add_field("id" , DataType.INT64, is_primary=True ) schema.add_field("text" , DataType.VARCHAR, max_length=65535 ) schema.add_field("vector" , DataType.FLOAT_VECTOR, dim=1024 ) client.create_collection( collection_name="my_docs" , schema=schema, index_params={ "field_name" : "vector" , "index_type" : "HNSW" , "metric_type" : "COSINE" , "params" : {"M" : 16 , "efConstruction" : 200 } } ) client.insert("my_docs" , [ {"id" : 1 , "text" : "人工智能正在改变世界..." , "vector" : embedding_1}, {"id" : 2 , "text" : "机器学习是 AI 的子领域..." , "vector" : embedding_2}, ]) results = client.search( collection_name="my_docs" , data=[query_embedding], limit=10 , output_fields=["text" ] )
4.2 Qdrant——Rust 写的性能怪兽 🦀 1 2 3 4 架构:单机为主(也支持分布式) 语言:Rust ⚡ 许可证:Apache 2.0 GitHub Stars:~22k
核心优势 :
🚀 Rust 实现 ——极致性能,内存安全,CPU 效率高
🎯 过滤能力业界第一 ——自定义 payload 索引 + 布尔表达式
📦 单体二进制 ——一个可执行文件,下载即用
🔒 内置访问控制 ——JWT 认证开箱即用
Qdrant 最特别的——Payload Filtering(载荷过滤) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from qdrant_client import QdrantClientfrom qdrant_client.models import Filter, FieldCondition, Rangeclient = QdrantClient("localhost" , port=6333 ) results = client.search( collection_name="products" , query_vector=query_emb, limit=10 , query_filter=Filter( must=[ FieldCondition( key="price" , range =Range(gte=100 , lte=500 ) ), FieldCondition( key="category" , match ={"value" : "electronics" } ), ], must_not=[ FieldCondition( key="brand" , match ={"value" : "山寨牌" } ) ] ) )
量化功能(2025 年新特性) :
Scalar Quantization :float32 → uint8,4x 压缩
Binary Quantization :float32 → 1 bit,32x 压缩
MMap :大索引不全部加载到内存
适合场景 :
✅ 中等数据量(10 万~5000 万)
✅ 需要复杂的元数据过滤
✅ 对性能有极致要求
✅ 需要快速部署(Docker 一键)
不适合 :
❌ 十亿级以上(还是 Milvus 更合适)
❌ 需要在浏览器端运行
4.3 LanceDB——新生代的全能选手 🏹 1 2 3 4 5 架构:嵌入式(无服务器) 语言:Rust 核心 + Python/JS/Java SDK 许可证:Apache 2.0 GitHub Stars:~5k 存储格式:Lance(列式存储,Apache Arrow 生态)
LanceDB 是我个人最看好 的年轻向量数据库。它的架构哲学和其他所有向量数据库都不一样:
1 2 3 4 5 6 7 传统向量数据库思维: 开一个服务器 → 连接 → 操作 (PostgreSQL / Redis 模式) LanceDB 的思路: import lancedb → 直接读写文件 (SQLite / DuckDB 模式)
这就是所谓的”无服务器(Serverless)向量数据库” ——不需要启动任何服务,一个 Python 包就够了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import lancedbimport numpy as npdb = lancedb.connect("./my_lancedb" ) table = db.create_table( "documents" , data=[{ "vector" : np.random.randn(1024 ).tolist(), "text" : "LanceDB 太方便了" , "id" : 1 }] ) table.create_index( metric="cosine" , num_partitions=256 , num_sub_vectors=64 ) results = table.search(query_vec).limit(10 ).to_pandas()
LanceDB 的独特功能 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1. 🗜️ Lance 列式存储格式 → 比 Parquet 更快的随机访问 → 原生支持的向量和 BLOB 类型 → 零拷贝读取(Arrow FFI) 2. 📊 完整的 SQL 支持(通过 DataFusion) SELECT text, vector FROM documents WHERE category = 'tech' ORDER BY l2_distance(vector, query_vec) LIMIT 10 3. 🔄 多模态向量支持 同一行可以有多个向量列: - text_embedding (1024维) - image_embedding (512维) - audio_embedding (768维) 4. 🌊 流式摄入 → 支持增量写入 → 支持数据版本管理(类似 Git 的 time travel)
适合场景 :
✅ 原型开发和个人项目(最方便)
✅ 客户端应用(Python/JS SDK 零依赖)
✅ 多模态数据(文本+图片+音频向量存在一起)
✅ CI/CD 中的向量数据测试
✅ 需要版本管理的数据集
不适合 (目前):
❌ 超高并发(嵌入式架构的天然限制)
❌ 分布式(LanceDB Cloud 还在发展中)
❌ 企业级的权限和安全管控
4.4 ChromaDB——AI 开发者的”初恋” 💚 1 2 3 4 架构:嵌入式(Python 优先) 语言:Python(核心是 hnswlib + DuckDB) 许可证:Apache 2.0 GitHub Stars:~18k
ChromaDB 是很多 AI 开发者的第一个向量数据库——因为它和 LangChain / LlamaIndex 的集成做得最好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import chromadbclient = chromadb.PersistentClient(path="./chroma_data" ) collection = client.get_or_create_collection("my_docs" ) collection.add( documents=["这是一段文本" , "另一段文本" ], embeddings=[[0.1 , 0.2 , ...], [0.3 , 0.4 , ...]], metadatas=[{"source" : "a.pdf" }, {"source" : "b.pdf" }], ids=["doc1" , "doc2" ] ) results = collection.query( query_texts=["什么是向量数据库?" ], n_results=10 )
ChromaDB 的定位 :
🎯 简单到极致 ——三行代码就能跑起来
🔌 生态最好 ——LangChain、LlamaIndex、Haystack 的默认选择
⚡ 快速原型 ——从想法到验证只需 5 分钟
但也有一些现实的短板 :
🐌 性能上限低(Python 实现的瓶颈)
📉 百万级以上数据量会明显变慢
🔧 生产部署不够成熟
一句话总结 :最好的入门选择,但大部分人最终会迁移到 Qdrant 或 Milvus。
4.5 pgvector——“我就要用 PostgreSQL!”党 🐘 1 2 3 架构:PostgreSQL 扩展 语言:C 许可证:PostgreSQL License
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 CREATE EXTENSION vector;CREATE TABLE documents ( id SERIAL PRIMARY KEY , content TEXT, embedding vector(1024 ) ); INSERT INTO documents (content, embedding)VALUES ('文档内容' , '[0.1, 0.2, 0.3, ...]' );CREATE INDEX ON documentsUSING hnsw (embedding vector_cosine_ops)WITH (m = 16 , ef_construction = 200 );SELECT content, 1 - (embedding <=> '[0.1, 0.2, ...]' ) AS similarityFROM documentsORDER BY embedding <=> '[0.1, 0.2, ...]' LIMIT 10 ;
最适合 :已经用 PostgreSQL 的团队,想加向量检索但不想引入新的基础设施。
局限性 :
HNSW 索引构建时锁表(PG 的固有限制)
性能不如专用向量数据库(但 pgvector 在 0.6+ 大幅改进)
没有分布式/分片支持(PG 本身的分片方案复杂)
4.6 Weaviate——“全自动”的向量数据库 🤖 1 2 3 4 架构:云原生 语言:Go 许可证:BSD-3 GitHub Stars:~12k
Weaviate 的最大特色是它的”自动向量化”——你不需要自己写 Embedding 逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import weaviateclient = weaviate.Client("http://localhost:8080" ) client.data_object.create( data_object={"content" : "这是一篇关于 AI 的文章" }, class_name="Document" , ) response = client.query.get( "Document" , ["content" ] ).with_near_text({ "concepts" : ["人工智能" ] }).with_limit(10 ).do()
特色功能 :
自动向量化 :内置集成 OpenAI、Cohere、HuggingFace 等 Embedding 服务
GraphQL API :比 REST 更灵活的查询方式
Hybrid Search (混合检索):一行命令即可开启向量 + BM25
多租户 :生产级的多用户隔离
不足 :
上手比 ChromaDB 复杂
Java/Python 二重生态,有些混乱
资源消耗较高(Java 基因)
4.7 选型速查表 🗺️
你的需求
推荐
理由
个人学习/原型
ChromaDB / LanceDB
最简洁,零配置
中小项目上线
Qdrant
性能好,部署简单
大厂/亿级数据
Milvus
唯一能打的分布式方案
已有 PostgreSQL
pgvector
不需要新服务
需要自动 Embedding
Weaviate
内置集成各种模型
客户端/JS 应用
LanceDB
WASM/JS SDK
多模态数据
LanceDB / Weaviate
原生支持多向量列
混合检索优先
Qdrant / Weaviate
过滤能力最强
极致性价比
LanceDB / Qdrant
单机就能跑得很好
4.8 一张表看清所有差异 📊
特性
Milvus
Qdrant
LanceDB
ChromaDB
pgvector
Weaviate
语言
Go+C++
Rust
Rust
Python
C
Go
架构
分布式
单机+分布式
嵌入式
嵌入式
PG 扩展
分布式
最小部署
Docker Compose
单容器
pip install
pip install
CREATE EXTENSION
Docker
索引
10+ 种
HNSW
IVF_PQ/DiskANN
HNSW
HNSW/IVF
HNSW
过滤
⭐⭐⭐⭐
⭐⭐⭐⭐⭐
⭐⭐⭐
⭐⭐
⭐⭐⭐⭐⭐
⭐⭐⭐⭐
混合检索
⭐⭐⭐
⭐⭐⭐⭐
⭐⭐⭐
⭐⭐
⭐⭐⭐
⭐⭐⭐⭐⭐
磁盘索引
✅ DiskANN
✅ MMap
✅ DiskANN
❌
❌
❌
量化
PQ/SQ
SQ/BQ
PQ
❌
❌
PQ/BQ/SQ
10万 QPS
✅
✅
❌(嵌入)
❌(嵌入)
⚠️
✅
亿级数据
✅
⚠️
⚠️
❌
❌
✅
GPU 加速
✅
❌
❌
❌
❌
❌
多模态
❌
⚠️
✅
❌
❌
✅
5. 性能调优实战手册 ⚡ 5.1 索引参数调优指南 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 场景 1:追求极致召回率(>99%) ───────────────────────────────── HNSW: M=64, ef_construction=500, ef_search=200 IVF: nlist=4096, nprobe=128 代价:内存增加约 3 倍,查询慢约 2 倍 场景 2:追求极致 QPS(>10000) ───────────────────────────────── HNSW: M=8, ef_construction=100, ef_search=20 IVF: nlist=256, nprobe=4 代价:召回率可能降到 85-90% 场景 3:平衡之选(推荐默认)✨ ───────────────────────────────── HNSW: M=16, ef_construction=200, ef_search=50 IVF: nlist=1024, nprobe=16 召回率 ~95%,查询 ~3ms,内存适中
5.2 向量化优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 for doc in documents: embedding = model.encode(doc) db.insert(embedding) batch_size = 256 for i in range (0 , len (documents), batch_size): batch = documents[i:i+batch_size] embeddings = model.encode(batch, show_progress_bar=False ) db.insert_batch(embeddings, batch) import torch.nn.functional as Fnormalized_emb = F.normalize(torch.tensor(emb), p=2 , dim=-1 ).numpy() model = SentenceTransformer("BAAI/bge-m3" , device="cuda" ) model.half()
5.3 常见性能瓶颈及解决方案
症状
可能原因
解决方案
查询慢(>100ms)
数据量超过内存
启用量化( PQ/SQ )或 DiskANN
插入慢
索引实时更新
批量插入后再建索引
内存爆炸
HNSW M 太大
降到 M=12,或换 IVF+PQ
召回率低(<80%)
ef_search 太小
增大到 100+
过滤后结果少
过滤条件太紧
先向量搜索,再过滤 (pre-filter → post-filter)
首次查询慢
索引未加载到内存
预热(warmup)查询
5.4 成本优化建议 💰 1 2 3 4 5 6 7 8 9 10 11 12 13 14 方案 A:全内存(HNSW,不做量化) 内存 = 向量数 × 维度 × 4 字节 × M 因子(~1.2) 1000万 × 1024 维 → 约 46 GB → 适合预算充足、追求极致性能的团队 方案 B:量化(IVF+PQ) 内存 = 向量数 × (压缩后每向量大小) 1000万 × 64 字节(PQ) → 约 640 MB → 适合大多数项目 方案 C:磁盘优先(DiskANN / MMap) 内存 = 图索引部分(~20%数据) + 热点缓存 1000万 × 1024 维 → 约 10 GB 内存 + 46 GB SSD → 适合预算敏感的大数据量场景
6. 新兴趋势与 2026 展望 🔮 6.1 Serverless 向量数据库 LanceDB 开创的”嵌入式”模式正在变成潮流。2025-2026 年我们看到:
越来越多的向量数据库提供”embedded mode”
向量数据库正在”SQLite 化”——从重型服务走向轻量库
6.2 多向量支持 传统模式:一个数据对象 → 一个向量 新模式:一个数据对象 → 多个向量(不同视角/模态)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 item = { "id" : "product_12345" , "title" : "超级好用的蓝牙耳机" , "text_embedding" : [0.1 , 0.2 , ...], "image_embedding" : [-0.3 , 0.5 , ...], "audio_embedding" : [0.8 , -0.1 , ...], "late_interaction_embedding" : [ [0.1 , 0.2 , ...], [0.3 , 0.4 , ...], ... ] }
ColBERT / ColPali 的 Token 级多向量 :
这是一个颠覆性的方向——不再把整段文本压缩成一个向量,而是每个 token 一个向量 。检索时做细粒度的 token 级别匹配:
1 2 3 4 5 传统:文档 → [一个向量] ColBERT:文档 → [[token1向量], [token2向量], [token3向量], ...] 优势:精确度大幅提升("MaxSim" 操作逐 token 计算最大相似度) 代价:存储增加 token_count 倍
6.3 向量数据库 + 全文检索的融合 2025 年的趋势是一个数据库同时搞定三种检索 :
%%{init: {'theme': 'base'}}%%
flowchart TB
RRF["🔀 RRF 融合排序"]
BM25["📝 BM25<br/>(关键词匹配)"] --> RRF
VEC["🧮 向量检索<br/>(语义相似)"] --> RRF
GRAPH["🕸️ 知识图谱<br/>(实体关联)"] --> RRF
RRF --> RESULT["✅ 统一检索结果"]
Elasticsearch 8.x 已经支持向量检索、Qdrant 增强了全文索引、Weaviate 的 hybrid search 越来越成熟。“All-in-One 检索数据库”的时代正在到来。
6.4 GPU 向量检索 2025 年 NVIDIA 的 cuVS(CUDA Vector Search)库可以直接在 GPU 上做向量检索:
1 2 3 4 传统:Embedding 在 GPU → 拷回 CPU → 向量数据库 CPU 检索 cuVS:Embedding 在 GPU → 直接在 GPU 上检索 → 无需拷贝! 性能提升:10-50x 加速(特别是在高维向量场景)
Milvus 2.4+ 已支持 GPU 索引,Qdrant 也在实验 GPU 加速。
6.5 边缘向量数据库 2026 年值得关注的方向:在手机、浏览器、IoT 设备 上跑的向量数据库。
LanceDB WASM :直接在浏览器里跑向量检索
ChromaDB light :Python 轻量版
SQLite-vec :SQLite 的向量扩展(纯 C 实现,可在任何平台编译)
想象一下:你的手机相册可以本地做”语义搜索”,不需要上传任何数据到云端。🔒
7. 动手实验:对比三个向量数据库 🧪 让我们用同一份数据对比 ChromaDB、Qdrant 和 LanceDB 的性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 """ 向量数据库 Benchmark 对比 ChromaDB / Qdrant / LanceDB 的插入和查询性能 """ import timeimport numpy as npfrom typing import List , Tuple N = 100_000 DIM = 1024 np.random.seed(42 ) train_vectors = np.random.randn(N, DIM).astype(np.float32) train_vectors = train_vectors / np.linalg.norm(train_vectors, axis=1 , keepdims=True ) query_vectors = np.random.randn(100 , DIM).astype(np.float32) query_vectors = query_vectors / np.linalg.norm(query_vectors, axis=1 , keepdims=True ) import chromadbchroma_client = chromadb.PersistentClient(path="./bench_chroma" ) chroma_collection = chroma_client.get_or_create_collection( "bench" , metadata={"hnsw:space" : "cosine" } ) t0 = time.time() batch_size = 5000 for i in range (0 , N, batch_size): batch = train_vectors[i:i+batch_size] chroma_collection.add( embeddings=batch.tolist(), ids=[f"vec_{j} " for j in range (i, min (i+batch_size, N))] ) chroma_insert_time = time.time() - t0 t0 = time.time() for qv in query_vectors: chroma_collection.query(query_embeddings=[qv.tolist()], n_results=10 ) chroma_query_time = time.time() - t0 import lancedblance_db = lancedb.connect("./bench_lancedb" ) data = [{"vector" : train_vectors[i].tolist(), "id" : i} for i in range (N)] t0 = time.time() lance_table = lance_db.create_table("bench" , data=data, mode="overwrite" ) lance_insert_time = time.time() - t0 t0 = time.time() lance_table.create_index( metric="cosine" , num_partitions=256 , num_sub_vectors=64 ) lance_index_time = time.time() - t0 t0 = time.time() for qv in query_vectors: lance_table.search(qv.tolist()).limit(10 ).to_list() lance_query_time = time.time() - t0 from qdrant_client import QdrantClientfrom qdrant_client.models import Distance, VectorParams, PointStructqdrant_client = QdrantClient("localhost" , port=6333 ) try : qdrant_client.delete_collection("bench" ) except : pass qdrant_client.create_collection( "bench" , vectors_config=VectorParams(size=DIM, distance=Distance.COSINE) ) t0 = time.time() points = [ PointStruct( id =i, vector=train_vectors[i].tolist() ) for i in range (N) ] qdrant_client.upsert("bench" , points) qdrant_insert_time = time.time() - t0 t0 = time.time() qdrant_client.create_payload_index( "bench" , field_name="id" , field_schema="integer" ) qdrant_index_time = time.time() - t0 t0 = time.time() for qv in query_vectors: qdrant_client.search("bench" , query_vector=qv.tolist(), limit=10 ) qdrant_query_time = time.time() - t0 print (f""" ╔══════════════════════════════════════════╗ ║ 向量数据库 Benchmark: {N:,} × {DIM} 维 ║ ╠══════════════════╦══════════╦═══════════╣ ║ 数据库 ║ 插入耗时 ║ 查询耗时 ║ ╠══════════════════╬══════════╬═══════════╣ ║ ChromaDB ║ {chroma_insert_time:6.1 f} s ║ {chroma_query_time:7.2 f} s ║ ║ LanceDB ║ {lance_insert_time:6.1 f} s ║ {lance_query_time:7.2 f} s ║ ║ Qdrant ║ {qdrant_insert_time:6.1 f} s ║ {qdrant_query_time:7.2 f} s ║ ╚══════════════════╩══════════╩═══════════╝ 注:LanceDB 索引构建耗时: {lance_index_time:.1 f} s Qdrant 索引构建耗时: {qdrant_index_time:.1 f} s (ChromaDB 索引为自动构建,无单独计时) 预期典型结果(10万×1024维): · 插入:Qdrant ~1s < LanceDB ~2s < ChromaDB ~5s · 查询:Qdrant ~0.5s < LanceDB ~1s < ChromaDB ~2s """ )
8. 总结:向量数据库的”道”与”术” 🎯 道(原则)
没有”最好的”向量数据库,只有”最适合当前场景”的
先想清楚你的数据量和查询模式,再选数据库
ANN 的本质是”用精度换速度”,99% 的场景不需要 100% 精确
向量数据库是基础设施,别在应用层做向量存储
术(实践)
学习阶段 → ChromaDB / LanceDB
项目上线 → Qdrant(单机性能王者)
规模化 → Milvus(分布式唯一选择)
PG 生态 → pgvector(足够了)
多模态 → LanceDB(理念最先进)
我的推荐链路 1 2 3 4 5 6 7 新手 → ChromaDB(3 天上手) ↓ 要上线了 → Qdrant(迁移代价小) ↓ 数据量爆了 → Milvus(专业的分布式方案) ↓ 成本砍不下去了 → LanceDB(Serverless + 磁盘索引)
感谢你看到这里!向量数据库的世界还在快速演进,本文会持续更新。如果你在选型中遇到困惑,欢迎评论区交流~ (◕‿◕✿)
觉得有用就点个赞吧,如果能帮到更多人那就太好啦 (๑˃̵ᴗ˂̵)و
参考资料 📚
Malkov & Yashunin - Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs (HNSW 原始论文) — 2016
Jégou et al. - Product Quantization for Nearest Neighbor Search — 2011
Microsoft - DiskANN: Fast Accurate Billion-point Nearest Neighbor Search on a Single Node — NeurIPS 2019
Milvus 官方文档
Qdrant 官方文档
LanceDB 官方文档
ChromaDB 官方文档
pgvector GitHub
NVIDIA cuVS — GPU 向量检索库
MTEB Leaderboard — Embedding 模型排行榜