一、简介
在RAG(检索增强生成)工作流中,向量数据库(Vector Database)是整个系统的记忆核心。它负责存储文档的向量表示,并提供高效的相似性检索能力。向量数据库的选择和使用直接影响RAG系统的性能、可扩展性和成本。
1.1 核心概念
向量数据库是一种专门设计用于存储、索引和查询高维向量的数据库系统。与传统的关系型数据库不同,向量数据库的核心操作是相似性搜索(Similarity Search),而不是精确匹配。
1.2 核心作用
在RAG工作流中:
- 文档被转换为向量:每个文档块通过嵌入模型变成高维向量
- 需要快速检索:用户查询时,需要在海量向量中找出最相似的几个
- 需要持久化存储:向量需要长期保存,而不是每次重新计算
传统数据库无法高效处理这种"找相似"的操作,而向量数据库专为此设计。
1.3 核心能力
| 能力 | 说明 | 重要性 |
|---|---|---|
| 向量存储 | 存储高维向量及其元数据 | ⭐⭐⭐⭐⭐ |
| 相似性搜索 | 快速找到与查询向量最相似的向量 | ⭐⭐⭐⭐⭐ |
| 混合搜索 | 结合向量相似度和标量过滤 | ⭐⭐⭐⭐ |
| 索引构建 | 建立高效的数据结构加速搜索 | ⭐⭐⭐⭐⭐ |
| 水平扩展 | 支持分布式部署和数据分片 | ⭐⭐⭐ |
| CRUD操作 | 增删改查向量数据 | ⭐⭐⭐ |
二、向量数据库的核心技术
2.1 索引算法
RAG 中向量检索的核心矛盾是 "精度" vs "速度":
- 精确检索:100%召回率,但计算量大,O(n)复杂度
- 近似最近邻检索:牺牲少量精度换取数量级的速度提升,90-99%召回率
主流索引算法对比:
| 索引算法 | 核心原理 | 精度 | 速度 | 内存占用 | 适用场景(RAG) |
|---|---|---|---|---|---|
| 暴力检索(BF) | 遍历所有向量计算相似度 | 100% | 极慢 | 低 | <1 万条向量、极致精度场景 |
| HNSW | 多层导航图检索 | 95-99% | 极快 | 中等 | 90% RAG 场景(首选) |
| IVF | 分桶聚类 + 桶内暴力检索 | 90-95% | 快 | 低 | 超大规模(1 亿 +)、低内存 |
| IVF-HNSW | IVF 分桶 + HNSW 桶内检索 | 95-98% | 极快 | 低 | 千万 / 亿级向量、高并发 |
| PQ | 向量压缩 + 编码检索 | 85-90% | 极快 | 极低 | 10 亿 + 向量、精度要求低 |
2.2 相似度度量
向量数据库支持的相似度度量:
| 度量 | 公式 | 特点 | 适用场景 |
|---|---|---|---|
| 余弦相似度 | cos(θ) = A·B/(|A||B|) | 关注方向,忽略长度 | 文本嵌入(最常用) |
| 欧氏距离(L2) | √Σ(A_i - B_i)² | 关注绝对距离 | 图像、特征向量 |
| 内积(IP) | Σ(A_i × B_i) | 同时考虑方向和长度 | 向量已归一化时等同于余弦 |
| 曼哈顿距离(L1) | Σ|A_i - B_i| | 对异常值鲁棒 | 稀疏向量 |
2.3 元数据过滤
元数据是附加在向量上的结构化信息,例如文档的来源、作者、发布时间、类别、权限标签等。元数据过滤指的是在向量相似性搜索的过程中,根据这些属性条件缩小搜索范围,只返回满足特定业务约束的向量。
向量数据库通常采用以下三种方式之一或组合来实现元数据过滤:
-
预过滤(Pre-filtering)
- 流程:先根据元数据条件从数据库中筛选出符合条件的向量 ID 列表,然后仅在这些 ID 对应的向量中进行近似最近邻搜索。
- 优点:确保最终结果都满足元数据条件,不会漏掉符合条件的向量。
- 缺点:如果元数据过滤后的候选集很小,向量索引可能无法充分利用,搜索精度可能下降(因为 ANN 索引通常依赖全局分布);同时两步操作可能增加延迟。
- 适用场景:过滤条件能够将候选集缩小到可接受的范围,且对准确性要求高。
-
后过滤(Post-filtering)
- 流程:先执行向量相似性搜索,返回 Top-K(通常 K 设置得比实际需要大一些),然后从这些结果中剔除不满足元数据条件的向量。
- 优点:实现简单,向量搜索本身保持高效,无需修改索引结构。
- 缺点:可能会丢失一些相似度高但被过滤掉的向量,尤其是当过滤条件比较严格时,Top-K 中可能包含大量不符合条件的向量,最终可用的结果不足 K 个。
- 适用场景:元数据条件较为宽松,或可接受一定的召回损失。
-
混合过滤(或称"索引级过滤")
- 流程:在向量索引构建阶段,就将元数据集成到索引结构中,使得搜索过程能同时考虑向量相似度和元数据条件。常见技术包括:
- 带过滤的 IVF(Inverted File Index):在 IVF 的每个聚类(Voronoi cell)中,除了存储向量,还存储该向量所属的元数据标签。搜索时只扫描满足元数据条件的向量所在的聚类。
- HNSW + 元数据位图:在 HNSW 图的节点上附加一个位图,标识节点是否满足某些元数据条件,搜索时动态跳过不满足条件的节点。
- 倒排索引 + 向量索引:为每个元数据值建立倒排列表,搜索时先根据元数据条件合并倒排列表得到候选 ID 集,再在这些 ID 上执行向量搜索(可视为预过滤的优化版,但元数据索引与向量索引协同设计)。
- 优点:搜索过程同时受元数据和向量相似度约束,既能保证结果满足条件,又能利用索引加速。
- 缺点:实现复杂,不同数据库支持程度不同,索引构建和维护成本略高。
- 适用场景:对准确性和性能都有较高要求,且元数据过滤是高频操作。
三、主流向量数据库对比
3.1 产品分类
| 类型 | 代表产品 | 特点 |
|---|---|---|
| 专用向量数据库 | Pinecone、Milvus、Qdrant、Weaviate | 专为向量设计,功能丰富 |
| 传统数据库扩展 | pgvector(PostgreSQL)、Elasticsearch | 集成现有生态 |
| 云服务 | Azure AI Search、Vertex AI Vector Search | 托管服务,开箱即用 |
| 轻量级/本地 | Chroma、FAISS、LanceDB | 开发友好,适合原型 |
3.2 详细对比表
| 特性 | Chroma | FAISS | Milvus | Pinecone | pgvector | Qdrant |
|---|---|---|---|---|---|---|
| 类型 | 嵌入式 | 库 | 分布式 | 托管 | 扩展 | 专用 |
| 部署方式 | 本地 | 本地 | 自托管/云 | 云 | 本地 | 自托管/云 |
| 开发友好度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 生产就绪 | ⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 水平扩展 | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
| 混合搜索 | ⭐⭐⭐ | ❌ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 内置UI | ❌ | ❌ | ✅(Attu) | ✅(控制台) | ❌ | ✅(Web UI) |
| 开源 | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| 流行度 | 极高 | 极高 | 高 | 高 | 中 | 中 |
3.3 选择指南
| 场景 | 推荐选择 | 理由 |
|---|---|---|
| 快速原型、本地开发 | Chroma | 简单易用,无依赖,代码量少 |
| 算法研究、高性能计算 | FAISS | 最强大的向量索引库,灵活可定制 |
| 生产环境、大规模数据 | Milvus / Qdrant | 分布式、高可用、云原生 |
| 不想自建、快速上线 | Pinecone | 全托管,运维零成本 |
| 已有PostgreSQL | pgvector | 复用现有数据库,无需新组件 |
| 需要全文搜索+向量 | Elasticsearch | 强大的文本搜索+向量能力 |
四、LangChain中的向量数据库使用
4.1 统一接口
LangChain为所有向量数据库提供了统一的接口:
python
from langchain_community.vectorstores import Chroma, FAISS, Milvus, Pinecone
# 所有向量存储都支持以下核心方法
vectorstore = SomeVectorStore.from_documents(
documents=docs, # 文档列表
embedding=embeddings # 嵌入模型
)
# 相似度搜索
docs = vectorstore.similarity_search(query, k=5)
# 带分数的相似度搜索
docs_with_scores = vectorstore.similarity_search_with_score(query, k=5)
# 根据向量搜索
docs = vectorstore.similarity_search_by_vector(embedding_vector, k=5)
# 添加文档
vectorstore.add_documents(new_docs)
# 删除文档
vectorstore.delete(ids)
# 作为检索器使用
retriever = vectorstore.as_retriever()
4.2 Chroma(本地开发首选)
Chroma是一个轻量级、嵌入式向量数据库,最适合本地开发和原型验证。
python
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
# 初始化
embeddings = OpenAIEmbeddings()
# 从文档创建
vectorstore = Chroma.from_documents(
documents=docs,
embedding=embeddings,
persist_directory="./chroma_db", # 持久化目录
collection_name="my_collection"
)
# 或从现有持久化目录加载
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
# 添加文档
vectorstore.add_documents(new_docs)
# 检索
results = vectorstore.similarity_search_with_score("查询", k=5)
for doc, score in results:
print(f"相似度: {score:.4f}, 内容: {doc.page_content[:100]}...")
# 持久化
vectorstore.persist()
# 带元数据过滤
results = vectorstore.similarity_search(
"查询",
k=5,
filter={"source": "handbook.pdf"} # 只从特定来源检索
)
Chroma配置参数:
python
# 创建集合时配置索引
vectorstore = Chroma.from_documents(
documents=docs,
embedding=embeddings,
collection_metadata={
"hnsw:space": "cosine", # 相似度度量:cosine, l2, ip
"hnsw:construction_ef": 200, # 构建参数
"hnsw:M": 16, # 图连接数
"hnsw:search_ef": 64 # 搜索参数
}
)
4.3 FAISS(高性能本地库)
FAISS(Facebook AI Similarity Search)是一个强大的向量索引库,适合需要精细控制索引的场景。
python
from langchain_community.vectorstores import FAISS
import faiss
# 基本用法
vectorstore = FAISS.from_documents(docs, embeddings)
# 保存和加载
vectorstore.save_local("faiss_index")
vectorstore = FAISS.load_local("faiss_index", embeddings)
# 使用不同索引类型
index = faiss.IndexHNSWFlat(768, 32) # 维度768,M=32
vectorstore = FAISS(
embedding_function=embeddings,
index=index,
docstore=docstore,
index_to_docstore_id=index_to_docstore_id
)
# 添加文档
vectorstore.add_documents(more_docs)
# 合并多个FAISS索引
another_store = FAISS.from_documents(other_docs, embeddings)
vectorstore.merge_from(another_store)
# 检索
docs = vectorstore.similarity_search_with_relevance_scores(
"查询",
k=5,
score_threshold=0.7 # 只返回相似度>0.7的
)
FAISS索引类型选择:
python
import faiss
dimension = 768
# 精确搜索(暴力计算)
index = faiss.IndexFlatL2(dimension) # L2距离
# 或
index = faiss.IndexFlatIP(dimension) # 内积(归一化后等于余弦)
# HNSW(推荐)
index = faiss.IndexHNSWFlat(dimension, 32) # M=32
# IVF(内存友好)
nlist = 100 # 聚类中心数
quantizer = faiss.IndexFlatL2(dimension)
index = faiss.IndexIVFFlat(quantizer, dimension, nlist)
index.train(vectors) # 需要训练
# PQ(极低内存)
m = 16 # 子量化器数
nbits = 8 # 每个子量化器的比特数
index = faiss.IndexPQ(dimension, m, nbits)
index.train(vectors) # 需要训练
4.4 Milvus(生产级分布式)
Milvus是专为生产环境设计的分布式向量数据库,支持云原生部署。
bash
from langchain_community.vectorstores import Milvus
from pymilvus import connections, utility
# 连接Milvus服务
connections.connect(
alias="default",
host="localhost",
port="19530"
)
# 从文档创建
vectorstore = Milvus.from_documents(
documents=docs,
embedding=embeddings,
collection_name="my_rag_collection",
connection_args={
"host": "localhost",
"port": "19530"
},
index_params={
"metric_type": "COSINE", # 相似度度量
"index_type": "HNSW", # 索引类型
"params": {"M": 16, "efConstruction": 200}
},
search_params={
"params": {"ef": 64}
}
)
# 检索
results = vectorstore.similarity_search_with_score(
"查询",
k=5,
param={"search_params": {"ef": 100}} # 动态调整搜索精度
)
# 带复杂过滤
results = vectorstore.similarity_search(
"查询",
k=5,
expr="source == 'handbook.pdf' and year > 2020" # 表达式过滤
)
# 删除集合
utility.drop_collection("my_rag_collection")
4.5 Pinecone(全托管云服务)
Pinecone是完全托管的向量数据库,无需运维,适合快速上线。
python
from langchain_pinecone import PineconeVectorStore
import pinecone
# 初始化Pinecone
pinecone.init(
api_key="your-api-key",
environment="your-environment"
)
# 创建索引(如果不存在)
index_name = "my-rag-index"
if index_name not in pinecone.list_indexes():
pinecone.create_index(
name=index_name,
dimension=1536, # 向量维度
metric="cosine", # 相似度度量
pods=1, # Pod数量
pod_type="p1.x1" # Pod类型
)
# 创建向量存储
vectorstore = PineconeVectorStore.from_documents(
documents=docs,
embedding=embeddings,
index_name=index_name
)
# 检索
docs = vectorstore.similarity_search("查询", k=5)
# 带元数据过滤
docs = vectorstore.similarity_search(
"查询",
k=5,
filter={"source": {"$eq": "handbook.pdf"}}
)
# 更新文档
vectorstore.add_documents(new_docs)
# 删除
vectorstore.delete(ids)
# 清理
pinecone.delete_index(index_name)
4.6 pgvector(PostgreSQL扩展)
如果已经在使用PostgreSQL,pgvector可以让你在现有数据库中增加向量能力。
python
from langchain_community.vectorstores import PGVector
# 数据库连接URL
CONNECTION_STRING = "postgresql+psycopg2://user:pass@localhost:5432/dbname"
# 创建向量存储
vectorstore = PGVector.from_documents(
documents=docs,
embedding=embeddings,
connection_string=CONNECTION_STRING,
collection_name="documents",
distance_strategy="cosine" # 或 "l2"
)
# 检索
docs = vectorstore.similarity_search("查询", k=5)
# 使用SQL直接查询
from sqlalchemy import text
with vectorstore._connect() as conn:
result = conn.execute(
text("""
SELECT content, 1 - (embedding <=> :query_embedding) as similarity
FROM documents
ORDER BY similarity DESC
LIMIT 5
"""),
{"query_embedding": query_vector}
)
五、高级功能与优化
5.1 HNSW索引参数
python
collection = client.create_collection(
name="optimized_collection",
metadata={
"hnsw:space": "cosine",
"hnsw:construction_ef": 200, # 构建时的搜索范围(越大索引质量越高,但构建越慢)
"hnsw:M": 32, # 每个节点的最大连接数(越大召回率越高,但内存占用越大)
"hnsw:search_ef": 100, # 查询时的搜索范围(越大召回率越高,但查询越慢)
"hnsw:num_threads": 4 # 构建时使用的线程数
}
)
参数说明:
- M:16-64之间,默认16。越大召回率越高,但内存占用越大
- construction_ef:100-500之间,默认100。越大索引质量越高
- search_ef:100-500之间,默认100。查询时可根据需要临时调整
5.2 IVF索引参数
python
index_params = {
"metric_type": "COSINE",
"index_type": "IVF_FLAT",
"params": {
"nlist": 4096, # 聚类中心数量(越大召回率越高,但查询越慢)
"nprobe": 16 # 查询时搜索的聚类中心数量(越大召回率越高)
}
}
5.3 PQ索引参数
python
index_params = {
"m": 8, # 子空间数,8-16
"nbits": 8 # 每个子空间的比特数,通常8
}
5.4 混合搜索(向量 + 关键词)
许多向量数据库支持混合搜索,结合向量相似度和关键词匹配。
python
# Qdrant 示例
from langchain_qdrant import QdrantVectorStore
from qdrant_client import QdrantClient
from qdrant_client.http.models import Filter, FieldCondition, MatchValue
vectorstore = QdrantVectorStore.from_documents(
docs,
embeddings,
url="http://localhost:6333",
collection_name="my_docs"
)
# 带关键词过滤
filter = Filter(
must=[
FieldCondition(
key="source",
match=MatchValue(value="handbook.pdf")
)
]
)
docs = vectorstore.similarity_search(
"查询",
k=5,
filter=filter
)
5.5 多向量检索
为每个文档存储多个向量(如摘要、关键句、全文),提高检索质量。
python
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.storage import InMemoryStore
from langchain_community.vectorstores import Chroma
# 创建多向量检索器
vectorstore = Chroma(collection_name="embeddings", embedding_function=embeddings)
store = InMemoryStore()
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
docstore=store,
id_key="doc_id"
)
# 为每个文档生成多个向量
doc_ids = ["doc1", "doc2"]
summaries = ["文档1摘要", "文档2摘要"]
key_sentences = ["关键句1", "关键句2"]
summary_vecs = embeddings.embed_documents(summaries)
sentence_vecs = embeddings.embed_documents(key_sentences)
# 添加多个向量
retriever.vectorstore.add_vectors(doc_ids, summary_vecs)
retriever.vectorstore.add_vectors(doc_ids, sentence_vecs)
# 存储原始文档
retriever.docstore.mset(list(zip(doc_ids, docs)))
5.6 父子文档检索
将文档切分成小块用于检索,但返回时返回包含上下文的父文档。
python
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
# 创建父子文档检索器
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
vectorstore = Chroma(collection_name="child_docs", embedding_function=embeddings)
store = InMemoryStore()
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter
)
# 添加文档
retriever.add_documents(docs)
# 检索(返回父文档)
parent_docs = retriever.get_relevant_documents("查询")
六、完整实战:构建生产级RAG向量存储
python
import os
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Milvus
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pymilvus import connections, utility
# 1. 准备数据
loader = TextLoader("knowledge_base.txt")
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"加载了 {len(chunks)} 个文档块")
# 2. 初始化嵌入模型
embeddings = OpenAIEmbeddings(
model="text-embedding-3-small",
dimensions=512 # 降维节省存储
)
# 3. 连接Milvus
connections.connect(
alias="default",
host=os.getenv("MILVUS_HOST", "localhost"),
port=os.getenv("MILVUS_PORT", "19530")
)
collection_name = "production_rag"
# 如果集合已存在,删除重建(仅示例)
if utility.has_collection(collection_name):
utility.drop_collection(collection_name)
# 4. 创建向量存储(配置优化)
vectorstore = Milvus.from_documents(
documents=chunks,
embedding=embeddings,
collection_name=collection_name,
index_params={
"metric_type": "COSINE",
"index_type": "HNSW",
"params": {
"M": 16,
"efConstruction": 200
}
},
search_params={
"params": {
"ef": 64
}
},
consistency_level="Eventually" # 一致性级别
)
print(f"向量存储创建完成,集合名: {collection_name}")
# 5. 创建检索器(带混合搜索能力)
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={
"k": 5,
"param": {"search_params": {"ef": 100}} # 动态提高搜索精度
}
)
# 6. 测试检索
test_queries = [
"公司年假政策",
"如何申请报销",
"医疗保险覆盖范围"
]
for query in test_queries:
docs_with_scores = vectorstore.similarity_search_with_score(query, k=3)
print(f"\n查询: {query}")
for doc, score in docs_with_scores:
print(f" 相似度: {score:.4f} - {doc.page_content[:50]}...")
# 7. 构建RAG链
prompt = ChatPromptTemplate.from_template("""
基于以下上下文回答问题。如果无法从上下文中找到答案,请说"我不知道"。
上下文:
{context}
问题:{input}
回答:""")
model = ChatOpenAI(model="gpt-4o")
document_chain = create_stuff_documents_chain(model, prompt)
rag_chain = create_retrieval_chain(retriever, document_chain)
# 8. 问答
response = rag_chain.invoke({"input": "年假有多少天?"})
print(f"\n答案: {response['answer']}")
# 9. 性能统计
import time
latencies = []
for query in test_queries:
start = time.time()
docs = retriever.get_relevant_documents(query)
end = time.time()
latencies.append((end - start) * 1000)
print(f"\n检索性能统计:")
print(f" 平均延迟: {np.mean(latencies):.2f} ms")
print(f" 最大延迟: {np.max(latencies):.2f} ms")
print(f" 最小延迟: {np.min(latencies):.2f} ms")
七、常见问题与解决方案
Q1: 向量不归一化,检索乱掉
解决方案:
- 必须开归一化。
python
encode_kwargs={"normalize_embeddings": True}
Q2: FAISS 不能分布式,不能多线程并发写
解决方案:
- 只适合开发,不适合高并发生产
Q3: 向量维度不匹配
解决方案:
- 确保使用相同的模型
python
assert len(vector) == collection_metadata["vector_dim"]
Q4: 不建索引,千万数据秒崩
解决方案:
- 必须建索引:HNSW / IVF
Q5: 元数据丢失,无法溯源
解决方案:
- 分块时一定要带:source、page、file_name、timestamp
Q6: 长文本直接嵌入
解决方案:
- 超过模型最大长度,必须先分块
Q7: 用 L2 距离做文本检索,效果极差
解决方案:
- RAG 只推荐:COSINE / IP