关键词:向量数据库、Milvus、Chroma、Pinecone、Qdrant、FAISS
目录
- 什么是向量数据库?
- [核心技术:ANN 近似最近邻搜索](#核心技术:ANN 近似最近邻搜索)
- 主流向量数据库全景
- Chroma:本地开发首选
- Milvus:生产级开源方案
- Pinecone:全托管云服务
- Qdrant:高性能新秀
- FAISS:纯向量检索库
- 性能基准测试
- 选型决策树
- 代码实战:四库横向对比
- 总结:我的选型建议
1. 什么是向量数据库?
向量数据库是专门为存储和检索高维向量而优化的数据库。
它解决什么问题?
传统数据库擅长精确匹配(WHERE id = 123),但无法处理"语义相似"的查询:
# 传统 SQL(无法做到)
SELECT * FROM documents WHERE content ≈ "人工智能技术"
# 向量数据库(可以做到)
query_vector = embed("人工智能技术")
results = vector_db.search(query_vector, top_k=5)
# 能找到"AI研究进展"、"机器学习算法"等语义相近的内容
向量数据库的核心能力:
- 高维向量存储:通常 128~4096 维
- 近似最近邻搜索:毫秒级返回最相似的 K 个向量
- 结合标量过滤:如"在 2024 年以后 AND 相似度 > 0.8"
2. 核心技术:ANN 近似最近邻搜索
暴力搜索(计算查询向量与所有向量的距离)精确但慢,千万级数据下无法接受。ANN 算法在牺牲极少精度的前提下,将搜索速度提升几个数量级。
主流 ANN 算法
HNSW(Hierarchical Navigable Small World):
- 原理:构建多层图结构,上层稀疏下层密集,搜索时从上往下导航
- 优点:精度高、查询速度快、支持动态插入
- 缺点:内存消耗大(图结构存于内存)
- 使用场景:Chroma、Qdrant、Milvus 默认索引
IVF(Inverted File Index):
- 原理:K-means 聚类,查询时只在最近的几个簇内搜索
- 优点:内存友好,适合磁盘存储
- 缺点:需要训练(静态数据),不支持实时插入
- 使用场景:FAISS、Milvus IVF_FLAT
PQ(Product Quantization):
- 原理:将向量压缩编码,以更小内存换取略低精度
- 优点:大幅降低内存(100x+),适合超大规模
- 组合使用:IVFPQ(最常用的大规模场景组合)
3. 主流向量数据库全景
| 数据库 | 类型 | 语言 | Star数 | 适合规模 | 特点 |
|---|---|---|---|---|---|
| Chroma | 开源/本地 | Python | 15k+ | <百万级 | 极简易用,开发首选 |
| Milvus | 开源/分布式 | Go/C++ | 30k+ | 亿级 | 生产级,功能全面 |
| Qdrant | 开源/云 | Rust | 20k+ | 千万级 | 高性能,过滤强 |
| Pinecone | 全托管SaaS | - | - | 亿级 | 零运维,最贵 |
| Weaviate | 开源/云 | Go | 12k+ | 千万级 | 内置向量化 |
| FAISS | 库(非DB) | C++/Python | 32k+ | 亿级+ | 仅算法,无服务 |
| pgvector | PostgreSQL扩展 | C | 12k+ | 百万级 | 已有PG则首选 |
4. Chroma:本地开发首选
核心特点
- 极简 API:5 行代码完成向量存储和检索
- 嵌入式运行:无需部署服务,Python 进程内运行
- 本地持久化:数据保存到本地目录
- 免费开源:MIT 协议
适合场景
✅ 个人项目、原型开发
✅ 数据量 < 100 万条
✅ 单机部署
❌ 不适合高并发生产环境
快速上手
python
import chromadb
from chromadb.utils import embedding_functions
# 方式1:内存中运行(重启后数据消失)
client = chromadb.Client()
# 方式2:本地持久化
client = chromadb.PersistentClient(path="./my_vector_db")
# 创建集合(类似数据库中的表)
collection = client.create_collection(
name="my_docs",
embedding_function=embedding_functions.OpenAIEmbeddingFunction(
api_key="your-key",
model_name="text-embedding-3-small"
)
)
# 添加文档
collection.add(
documents=[
"Python 是一种解释型编程语言",
"机器学习是人工智能的子领域",
"深度学习使用神经网络",
],
ids=["doc1", "doc2", "doc3"],
metadatas=[
{"source": "wiki", "category": "programming"},
{"source": "wiki", "category": "AI"},
{"source": "wiki", "category": "AI"},
]
)
# 查询(带元数据过滤)
results = collection.query(
query_texts=["什么是神经网络?"],
n_results=2,
where={"category": "AI"} # 只在AI类别中搜索
)
for doc, distance in zip(results["documents"][0], results["distances"][0]):
print(f"相似度: {1-distance:.3f} | 内容: {doc}")
5. Milvus:生产级开源方案
核心特点
- 分布式架构:支持水平扩展,处理亿级向量
- 多索引支持:HNSW、IVF_FLAT、IVF_SQ8、IVFPQ 等
- 丰富的标量过滤:支持复杂的布尔表达式
- GPU 加速:支持 GPU 索引和搜索
适合场景
✅ 企业生产环境
✅ 数据量 > 千万条
✅ 需要高并发
✅ 需要精细控制索引参数
❌ 运维成本较高(需要 Docker/K8s)
快速上手
python
from pymilvus import (
connections, CollectionSchema, FieldSchema, DataType,
Collection, utility
)
import numpy as np
# 连接 Milvus(需要先部署:docker run -d milvusdb/milvus)
connections.connect("default", host="localhost", port="19530")
# 定义 Schema
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=2000),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536),
FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=50),
]
schema = CollectionSchema(fields, description="文档向量库")
# 创建集合
collection_name = "my_documents"
if utility.has_collection(collection_name):
utility.drop_collection(collection_name)
collection = Collection(collection_name, schema)
# 创建 HNSW 索引
index_params = {
"metric_type": "COSINE",
"index_type": "HNSW",
"params": {
"M": 16, # 每个节点的最大边数,越大精度越高内存越大
"efConstruction": 200 # 建索引时的搜索范围
}
}
collection.create_index("embedding", index_params)
# 插入数据
texts = ["Python编程语言", "机器学习算法", "深度神经网络"]
embeddings = np.random.rand(3, 1536).tolist() # 实际使用 Embedding 模型
categories = ["programming", "AI", "AI"]
collection.insert([texts, embeddings, categories])
collection.load()
# 搜索(带过滤条件)
search_params = {"metric_type": "COSINE", "params": {"ef": 64}}
query_embedding = np.random.rand(1, 1536).tolist()
results = collection.search(
data=query_embedding,
anns_field="embedding",
param=search_params,
limit=5,
expr='category == "AI"', # 标量过滤
output_fields=["content", "category"]
)
for hits in results:
for hit in hits:
print(f"ID: {hit.id}, 相似度: {hit.score:.4f}, 内容: {hit.entity.get('content')}")
Milvus Lite(轻量版)
不想部署完整 Milvus?Milvus Lite 可以像 SQLite 一样嵌入式使用:
python
from pymilvus import MilvusClient
# 本地文件存储,无需部署服务
client = MilvusClient("./milvus_demo.db")
6. Pinecone:全托管云服务
核心特点
- 零运维:完全托管,无需操心基础设施
- 高可用:99.99% SLA
- 实时更新:数据插入后立即可搜索
- 按量计费:免费套餐可用(1个index,100k向量)
适合场景
✅ 不想管运维的团队
✅ 快速上线的 SaaS 产品
✅ 数据安全要求不高
❌ 成本较高(大规模下)
❌ 数据出境合规问题
快速上手
python
from pinecone import Pinecone, ServerlessSpec
# 初始化
pc = Pinecone(api_key="your-pinecone-api-key")
# 创建索引(Serverless 无需管理服务器)
index_name = "my-index"
if index_name not in pc.list_indexes().names():
pc.create_index(
name=index_name,
dimension=1536,
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1")
)
index = pc.Index(index_name)
# Upsert 向量
vectors = [
{"id": "doc1", "values": [0.1]*1536, "metadata": {"text": "Python编程", "tag": "code"}},
{"id": "doc2", "values": [0.2]*1536, "metadata": {"text": "机器学习", "tag": "AI"}},
]
index.upsert(vectors=vectors)
# 查询
results = index.query(
vector=[0.15]*1536,
top_k=3,
filter={"tag": {"$eq": "AI"}}, # 元数据过滤
include_metadata=True
)
for match in results["matches"]:
print(f"ID: {match['id']}, 分数: {match['score']:.4f}, 内容: {match['metadata']['text']}")
7. Qdrant:高性能新秀
Qdrant 用 Rust 实现,以高性能和强大的过滤能力著称:
python
from qdrant_client import QdrantClient
from qdrant_client.models import (
Distance, VectorParams, PointStruct, Filter, FieldCondition, MatchValue
)
# 本地运行(无需外部服务)
client = QdrantClient(":memory:") # 内存模式
# client = QdrantClient(path="./qdrant_storage") # 本地持久化
# client = QdrantClient(host="localhost", port=6333) # 连接服务
# 创建集合
client.create_collection(
collection_name="documents",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)
# 插入向量
points = [
PointStruct(
id=1,
vector=[0.1] * 1536,
payload={"text": "Python编程", "category": "programming", "year": 2024}
),
PointStruct(
id=2,
vector=[0.2] * 1536,
payload={"text": "机器学习", "category": "AI", "year": 2024}
),
]
client.upsert(collection_name="documents", points=points)
# 带复杂过滤的搜索
results = client.search(
collection_name="documents",
query_vector=[0.15] * 1536,
limit=3,
query_filter=Filter(
must=[
FieldCondition(key="category", match=MatchValue(value="AI")),
]
),
with_payload=True
)
for result in results:
print(f"ID: {result.id}, 分数: {result.score:.4f}, 内容: {result.payload}")
8. FAISS:纯向量检索库
FAISS 是 Meta 开源的向量检索库,不是数据库,没有持久化、过滤等功能,但性能极强:
python
import faiss
import numpy as np
# 创建 HNSW 索引
dimension = 1536
index = faiss.IndexHNSWFlat(dimension, 32) # M=32
# 添加向量
vectors = np.random.rand(10000, dimension).astype('float32')
faiss.normalize_L2(vectors) # 余弦相似度需归一化
index.add(vectors)
# 搜索
query = np.random.rand(1, dimension).astype('float32')
faiss.normalize_L2(query)
k = 5
distances, indices = index.search(query, k)
print(f"最相似的 {k} 个向量索引:{indices[0]}")
print(f"相似度分数:{distances[0]}")
# 保存/加载索引
faiss.write_index(index, "my_index.faiss")
loaded_index = faiss.read_index("my_index.faiss")
9. 性能基准测试
以下数据来自开源基准测试 ann-benchmarks(仅供参考):
数据集:100 万向量,维度 768,召回率 @10
| 数据库 | QPS(查询/秒) | P99 延迟 | 内存占用 | 召回率 |
|---|---|---|---|---|
| Qdrant(HNSW) | ~8,000 | 8ms | 4.2GB | 97% |
| Milvus(HNSW) | ~6,500 | 12ms | 4.8GB | 96% |
| Pinecone | ~5,000 | 15ms | 托管 | 96% |
| Chroma(HNSW) | ~2,000 | 25ms | 3.8GB | 95% |
| FAISS(GPU) | ~50,000+ | <1ms | 3.2GB | 98% |
⚠️ 注意:实际性能受硬件、配置、数据分布影响很大,以上仅为量级参考。
10. 选型决策树
我的向量数据量是多少?
├── < 10 万:Chroma 或 pgvector(如果已有PostgreSQL)
├── 10 万 ~ 千万
│ ├── 有运维能力?→ Qdrant 或 Milvus Lite
│ └── 无运维能力?→ Pinecone 免费套餐
└── > 千万
├── 自建?→ Milvus(分布式)
└── 托管?→ Pinecone / Zilliz Cloud(Milvus托管)
其他考量:
├── 数据安全/合规 → 选开源自部署(Milvus/Qdrant)
├── 极致性能 → Qdrant(Rust实现)或 FAISS
├── 快速开发/原型 → Chroma
├── 已有PostgreSQL → pgvector(零额外运维)
└── 超大规模离线计算 → FAISS
11. 代码实战:四库横向对比
python
"""
四个向量数据库的统一接口封装,方便切换
"""
from abc import ABC, abstractmethod
from typing import List, Tuple
import numpy as np
class VectorStore(ABC):
"""向量数据库统一接口"""
@abstractmethod
def add(self, texts: List[str], vectors: List[List[float]], ids: List[str]):
pass
@abstractmethod
def search(self, query_vector: List[float], top_k: int = 5) -> List[Tuple[str, float]]:
"""返回 [(text, score), ...]"""
pass
class ChromaStore(VectorStore):
def __init__(self, collection_name: str = "default"):
import chromadb
self.client = chromadb.Client()
self.collection = self.client.create_collection(collection_name)
def add(self, texts, vectors, ids):
self.collection.add(documents=texts, embeddings=vectors, ids=ids)
def search(self, query_vector, top_k=5):
results = self.collection.query(query_embeddings=[query_vector], n_results=top_k)
return list(zip(results["documents"][0],
[1 - d for d in results["distances"][0]]))
class QdrantStore(VectorStore):
def __init__(self, collection_name: str = "default", dim: int = 128):
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams
self.client = QdrantClient(":memory:")
self.collection_name = collection_name
self.client.create_collection(
collection_name=collection_name,
vectors_config=VectorParams(size=dim, distance=Distance.COSINE)
)
def add(self, texts, vectors, ids):
from qdrant_client.models import PointStruct
points = [
PointStruct(id=i, vector=v, payload={"text": t})
for i, (t, v) in enumerate(zip(texts, vectors))
]
self.client.upsert(collection_name=self.collection_name, points=points)
def search(self, query_vector, top_k=5):
results = self.client.search(
collection_name=self.collection_name,
query_vector=query_vector,
limit=top_k,
with_payload=True
)
return [(r.payload["text"], r.score) for r in results]
class FAISSStore(VectorStore):
def __init__(self, dim: int = 128):
import faiss
self.index = faiss.IndexFlatIP(dim) # 内积(归一化后等于余弦)
self.texts = []
def add(self, texts, vectors, ids):
vecs = np.array(vectors, dtype='float32')
faiss.normalize_L2(vecs)
self.index.add(vecs)
self.texts.extend(texts)
def search(self, query_vector, top_k=5):
q = np.array([query_vector], dtype='float32')
faiss.normalize_L2(q)
scores, indices = self.index.search(q, top_k)
return [(self.texts[i], float(scores[0][j]))
for j, i in enumerate(indices[0]) if i < len(self.texts)]
# 测试
def benchmark(store: VectorStore, n_docs: int = 1000, dim: int = 128):
import time
# 生成测试数据
texts = [f"文档 {i}" for i in range(n_docs)]
vectors = np.random.rand(n_docs, dim).tolist()
ids = [str(i) for i in range(n_docs)]
# 插入
t0 = time.time()
store.add(texts, vectors, ids)
insert_time = time.time() - t0
# 查询
query = np.random.rand(dim).tolist()
t0 = time.time()
for _ in range(100):
results = store.search(query, top_k=5)
search_time = (time.time() - t0) / 100
print(f"插入 {n_docs} 条耗时: {insert_time:.3f}s | 单次查询耗时: {search_time*1000:.2f}ms")
return results
print("=== Chroma ===")
chroma = ChromaStore()
benchmark(chroma)
print("=== FAISS ===")
faiss_store = FAISSStore(dim=128)
benchmark(faiss_store)
12. 总结:我的选型建议
场景对应推荐
| 情况 | 推荐选择 | 理由 |
|---|---|---|
| 学习/玩具项目 | Chroma | 最简单,3 行代码跑起来 |
| 本地知识库(<100w条) | Chroma 或 Qdrant | 本地运行,无需云服务 |
| 创业公司快速上线 | Pinecone | 零运维,快速迭代 |
| 中大型企业私有化部署 | Milvus | 功能最全,社区最活跃 |
| 追求极致性能 | Qdrant | Rust实现,性能顶尖 |
| 离线大规模向量计算 | FAISS | 纯算法库,性能无敌 |
| 已有 PostgreSQL | pgvector | 无额外运维成本 |