向量数据库深入指南

“向量数据库是 RAG 的记忆中枢——它存储了知识的向量表示,让 AI 能够快速检索。”

一、为什么需要向量数据库?

传统数据库存储的是结构化数据(表、行列),查询用的是精确匹配(WHERE id = 1)。

向量数据库存储的是高维向量(通常 768~4096 维),查询的是近似匹配——找到”最相似”的向量。

1
2
3
4
5
6
7
8
传统查询:
"找出 id = 123 的用户" → 精确匹配 ✓

向量查询:
"找出与 '机器学习' 最相关的 10 篇文章" → 近似匹配

语义相似 ≠ 字面相同
"深度学习" 和 "机器学习" 在向量空间中很接近

在大模型和 RAG 场景中,向量数据库负责高效存储和检索知识的向量表示

二、向量相似度度量

2.1 余弦相似度(Cosine Similarity)

衡量两个向量方向的相似度,取值范围 [-1, 1]。

1
2
3
4
5
6
cosine_similarity(A, B) = (A · B) / (||A|| × ||B||)

向量 A = [1, 0]
向量 B = [1, 0] → cosine = 1.0(完全相同方向)
向量 C = [0, 1] → cosine = 0.0(垂直)
向量 D = [-1, 0] → cosine = -1.0(完全相反)

适用场景:文本 embedding(如 sentence-transformers),因为 embedding 模型通常对向量做 L2 归一化。

2.2 点积(Dot Product)

直接计算向量内积,取值范围无界限。

1
dot_product(A, B) = Σ(A_i × B_i)

注意:如果向量未归一化,点积会偏向长的向量( magnitude 大的向量相似度分数更高)。通常需要先做归一化。

2.3 欧几里得距离(L2 Distance)

衡量向量间的直线距离。

1
L2_distance(A, B) = √(Σ(A_i - B_i)²)

适用场景:图像向量、语音嵌入。当”距离”有物理含义时(如人脸识别),L2 通常更好。

2.4 如何选择?

度量方式 适用场景 注意事项
余弦相似度 文本、推荐系统 已归一化的向量
点积 未归一化的向量、需要放大差异 先归一化
L2 距离 图像、人脸、语音 对 magnitude 敏感

三、向量索引算法

3.1 暴力搜索的问题

假设有 100 万个向量,每个向量 768 维。找 top-1 最相似需要计算 100 万次点积——这在实时场景中不可接受。

1
2
3
4
5
暴力搜索复杂度:O(N × D)
N = 向量数量
D = 向量维度

100万向量 × 768维 = 7.68亿次乘法

向量索引的目标:用大幅降低计算量的方式,找到”近似”最近邻——这就是 ANN(Approximate Nearest Neighbor,近似最近邻)。

3.2 IVF(Inverted File Index)

核心思想:将向量空间划分成多个聚类( Voronoi cells),检索时只搜索最近的几个聚类,而不是全部。

1
2
3
4
5
6
7
8
9
10
11
训练过程:
1. 用 K-Means 将所有向量分成 N 个聚类(如 1024 个)
2. 每个聚类有一个中心点(centroid)
3. 每个向量归属于离它最近的中心点对应的聚类

检索过程:
1. 计算 query 和所有中心点的距离,找到最近的 K 个聚类
2. 只在这 K 个聚类的向量中搜索
(K=1 就是只搜一个聚类,K=N 就是暴力搜索)

时间复杂度:O(N × D / K) ← 只搜 K 个聚类

IVF 的参数

  • nlist:聚类数量(太多→聚类不准,太少→搜索范围大)
  • nprobe:搜索时查几个聚类(越大越准,越慢)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import faiss

# 构建 IVF 索引
d = 768 # 向量维度
nlist = 1024 # 聚类数量

quantizer = faiss.IndexFlatIP(d) # 用内积作为距离度量
index = faiss.IndexIVFFlat(quantizer, d, nlist)

# 训练(需要足够多的向量)
index.train(train_vectors)
index.add(vectors)

# 检索
index.nprobe = 16 # 搜索 16 个最近的聚类
D, I = index.search(query_vectors, k=10)

3.3 HNSW(Hierarchical Navigable Small World)

核心思想:构建一个多层级图结构,从高层到底层逐步逼近目标。

1
2
3
4
5
6
HNSW 结构:
Layer 3: ○───────○───────○ ← 最稀疏,只有关键连接
/│ │ │\
Layer 2: ○─○──○───○─○──○───○─○ ← 中层
/││\ │ /││\ │ /││\
Layer 1: ○─○─○─○─○─○─○─○─○─○─○─○─○ ← 最密集

检索过程(从顶层开始):

1
2
3
4
5
1. 从 Layer 3 的某个入口点开始
2. 找到当前节点的最近邻(贪心搜索)
3. 如果找不到更近的节点,下降到 Layer 2
4. 重复,直到 Layer 0
5. 在 Layer 0 做穷举搜索找到最近邻

为什么快?

  • 高层节点稀疏,可以用大步长快速定位”大致区域”
  • 底层节点密集,确保找到真正的最近邻
  • 图结构保证了搜索路径的连通性

HNSW 的参数:

  • M:每个节点的最大连接数(越大越准,越耗内存)
  • efConstruction:构建时的搜索范围(越大越准,越慢)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import faiss

# 构建 HNSW 索引
d = 768
M = 32 # 每个节点的连接数
efConstruction = 200 # 构建参数

index = faiss.IndexHNSWFlat(d, M)
index.hnsw.efConstruction = efConstruction
index.add(vectors)

# 检索
index.hnsw.efSearch = 200 # 搜索参数(越大越准,越慢)
D, I = index.search(query_vectors, k=10)

3.4 PQ(Product Quantization)

核心思想:将高维向量”压缩”成短编码,检索时在压缩空间中进行距离计算。

1
2
3
4
5
6
7
8
9
原始向量:768 维 × 4 字节 = 3072 字节

PQ 压缩:
1. 将向量切成 M 段(如 M=8,每段 96 维)
2. 每段独立做 K-Means 聚类(如 256 个簇)
3. 用簇中心 ID(1 字节)代替原始向量
4. 最终编码:M × 1 字节 = 8 字节

压缩率:3072 / 8 = 384 倍!

检索过程:

1
2
3
1. query 也切成 M 段
2. 计算 query 每段和对应簇中心的距离(查表)
3. 累加各段距离,找到最近的编码

PQ 的问题是有损压缩,精度不如原始向量。适合海量数据+内存受限的场景。

1
2
3
4
5
6
7
8
9
10
11
12
import faiss

# PQ 索引
d = 768
m = 96 # 分段数
nbits = 8 # 每个段 2^8=256 个簇

index = faiss.IndexPQ(d, m, nbits)
index.train(train_vectors)
index.add(vectors)

D, I = index.search(query_vectors, k=10)

3.5 组合索引:IVF + HNSW + PQ

实际生产中,通常组合使用多种算法:

1
2
3
4
5
6
7
8
9
10
11
12
# 方案:IVF + HNSW(先用 IVF 缩小范围,再用 HNSW 精细搜索)
d = 768
nlist = 4096 # IVF 聚类数
M = 32 # HNSW 连接数

quantizer = faiss.IndexHNSWFlat(d, M) # 用 HNSW 作为量化器
index = faiss.IndexIVFPQ(quantizer, d, nlist, 96, 8)
index.train(vectors)
index.add(vectors)

index.nprobe = 64 # 搜索 64 个聚类
D, I = index.search(query_vectors, k=10)

3.6 索引算法对比

算法 精度 速度 内存 适用场景
暴力搜索(Flat) 100% 最慢 最大 小数据集、精确匹配
IVF ~95-99% 中等规模、平衡型
HNSW ~95-99% 最快 小-中等规模、高QPS
PQ ~80-95% 最快 最小 海量数据、内存受限
IVF+HNSW ~95-99% 生产环境推荐

四、主流向量数据库对比

4.1 Milvus

特点:开源、功能最全、性能最强,适合大规模生产部署。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pymilvus import connections, Collection

# 连接
connections.connect("default", host="localhost", port="19530")

# 加载集合
collection = Collection("articles")
collection.load()

# 搜索
search_params = {"metric_type": "IP", "params": {"nprobe": 16}}
results = collection.search(
data=[query_vector],
anns_field="embedding",
param=search_params,
limit=10,
expr=None
)

适用场景:大规模(1B+ 向量)、需要精细控制、 Kubernetes 部署。

4.2 Pinecone

特点:全托管云服务、零运维、简单易用,适合快速启动。

1
2
3
4
5
6
7
8
9
10
11
12
import pinecone

# 初始化
pinecone.init(api_key="...", environment="us-west1")
index = pinecone.Index("articles")

# 搜索
results = index.query(
vector=query_vector,
top_k=10,
include_metadata=True
)

适用场景:不想运维、快速原型、中小规模(免费层够用)。

4.3 Weaviate

特点:内置向量化和 GraphQL API,支持多种 embedding 模型,适合语义搜索 + 结构化查询结合的场景。

1
2
3
4
5
6
7
8
9
10
import weaviate

client = weaviate.Client("http://localhost:8080")

# 搜索
result = client.query.get(
"Article", ["title", "content"]
).with_near_vector({
"vector": query_vector
}).with_limit(10).do()

4.4 Chroma

特点:轻量级、纯 Python、易嵌入现有应用,适合本地开发和测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import chromadb

client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection("articles")

# 添加
collection.add(
ids=["1", "2"],
embeddings=[vec1, vec2],
metadatas=[{"title": "A"}, {"title": "B"}],
documents=["doc1", "doc2"]
)

# 搜索
results = collection.query(
query_embeddings=[query_vector],
n_results=10
)

4.5 对比总结

特性 Milvus Pinecone Weaviate Chroma
部署方式 自托管/云 全托管云 自托管/云 本地/嵌入式
规模 1B+ 100M+ 10M+ 1M 以下
性能 极高
运维复杂度 极低
费用 开源免费 按量付费 开源免费 免费
额外功能 混合搜索、分片 自动扩缩容 GraphQL 轻量集成

五、向量数据库的核心操作

5.1 CRUD 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 添加向量
index.insert([
{"id": "1", "vector": vec1, "metadata": {"text": "文档内容"}},
{"id": "2", "vector": vec2, "metadata": {"text": "另一篇"}},
])

# 查询向量
results = index.query(vector=query_vec, top_k=10)

# 更新向量
index.update([{"id": "1", "vector": new_vec, "metadata": {"text": "更新后"}}])

# 删除向量
index.delete(ids=["1"])

5.2 过滤(Metadata Filtering)

向量数据库通常支持先过滤再检索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 只在 "category == 'AI'" 的文档中搜索
results = index.query(
vector=query_vector,
top_k=10,
filter={"category": {"$eq": "AI"}}
)

# 复杂过滤条件
filter = {
"$and": [
{"category": {"$eq": "AI"}},
{"publish_year": {"$gte": 2023}},
{"author": {"$in": ["张三", "李四"]}}
]
}

注意:过滤操作通常无法利用向量索引(需要遍历),大量过滤会导致性能下降。最佳实践是先用向量检索快速召回候选,再用过滤筛选

5.3 分区和分片

当数据量非常大时,需要分区(partitioning)或分片(sharding):

1
2
3
4
5
6
# Milvus 分区
collection.create_partition("partition_2024") # 按年份分区
collection.insert([...], partition_name="partition_2024")

# 搜索时指定分区
collection.search(..., partitions=["partition_2024"])
1
2
3
4
5
6
7
8
9
10
# Milvus 分片(分布式)
# config.yaml
etcd:
address: etcd:2379
minio:
address: minio:9000

# 按 ID 范围分片
shard_nums = 4 # 4 个分片
collection = Collection("articles", shards_num=shard_nums)

六、Embedding 模型选择

6.1 常用文本 Embedding 模型

模型 维度 适用语言 特点
text-embedding-ada-002 1536 多语言 OpenAI 官方,稳定可靠
text-embedding-3-large 3072 多语言 OpenAI 最新,更强
bge-large-zh 1024 中英 国产开源,中文优秀
m3e-large 1024 中英 国产开源,轻量快速
e5-base-v2 768 多语言 微软,适合检索

6.2 Embedding 质量评估

使用 MTEB(Benchmark for Massive Text Embedding)评估:

1
2
3
4
5
6
7
8
9
10
from mteb import MTEB
from sentence_transformers import SentenceTransformer

model = SentenceTransformer('BAAI/bge-large-zh-v1.5')

# 评估任务
evaluation = MTEB(task_names=["Retrival"])
results = evaluation.run(model, output_folder=f"results/{model_name}")

print(results)

6.3 重编码(Re-embedding)问题

向量数据库中存储的 embedding 是静态的,但:

  • embedding 模型在更新
  • 文档内容在变化

解决方案

1
2
3
4
5
6
7
8
9
10
# 定期重新生成所有向量(Re-indexing)
def reindex_documents(collection, documents, embedder):
new_embeddings = embedder.embed(documents)

# 全量更新
collection.delete(all=True)
collection.add([
{"id": str(i), "vector": vec, "text": doc}
for i, (vec, doc) in enumerate(zip(new_embeddings, documents))
])

七、实战经验

7.1 选型建议

1
2
3
4
5
6
7
数据量 < 100万 → Chroma(最简单)
100万 ~ 1亿 → Pinecone / Weaviate(云服务或自托管)
1亿以上 → Milvus(必须分布式)

需要混合查询(向量+结构化)→ Weaviate / Milvus
追求最低成本 → 自托管(Milvus/Weaviate)
最快上线 → Pinecone

7.2 性能调优

1
2
3
4
5
6
7
8
9
10
11
12
13
# Milvus 调参建议
search_params = {
"metric_type": "IP", # 内积(需要先归一化)
"params": {
"nprobe": 16, # IVF: 搜索 16 个聚类(越大越准,越慢)
"ef": 200 # HNSW: 搜索范围(越大越准,越慢)
}
}

# 经验值
# QPS < 100: 不用过度优化
# QPS 100~1000: nprobe=16~64, ef=100~200
# QPS > 1000: 需要批量检索 + 异步处理

7.3 常见问题

Q:向量数据库和普通数据库有什么区别?

1
2
普通数据库:精确匹配 WHERE id = 1
向量数据库:近似匹配 "找出和这个向量最相似的 10 个"

Q:Embedding 怎么存?

1
2
3
通常存在向量数据库中,但也可以:
- PostgreSQL + pgvector(不想多引入一个数据库时)
- Elasticsearch(已有 ES 集群时)

Q:向量数据库能保证精确搜索吗?

1
2
不能保证。ANN 算法是"近似"最近邻,不是精确最近邻。
如果需要 100% 精确,用 Flat 索引(小数据集)或接受近似结果。

八、总结

向量数据库是 RAG 和语义搜索的核心基础设施:

  1. 度量选择:余弦相似度最常用(文本),L2 适合图像/语音
  2. 索引算法:HNSW(快速高精度)、IVF(省内存)、PQ(海量压缩)
  3. 选型:小规模用 Chroma,中等用 Pinecone/Weaviate,超大规模用 Milvus
  4. 过滤:metadata filtering 很有用,但要注意性能影响
  5. Embedding 模型:中文推荐 bge-large-zh,多语言用 text-embedding-3-large

相关文章: