12-向量数据库¶
面向 AI 时代的新一代数据库技术:向量相似性搜索与检索增强生成(RAG)
1. 向量数据库概述¶
1.1 什么是向量数据库¶
向量数据库是专门用于存储、索引和查询高维向量(Embedding)的数据库系统。与传统关系型数据库通过精确匹配进行查询不同,向量数据库通过计算向量之间的相似度来检索最相关的结果。
核心能力: - 高维向量存储(通常 128-4096 维) - 近似最近邻搜索(ANN) - 毫秒级检索延迟 - 支持十亿级向量规模 - 元数据过滤 + 向量搜索的混合查询
1.2 典型应用场景¶
| 场景 | 说明 |
|---|---|
| RAG(检索增强生成) | 为 LLM 提供外部知识检索 |
| 语义搜索 | 基于含义而非关键词的搜索 |
| 推荐系统 | 基于内容或用户向量的相似推荐 |
| 图像检索 | 以图搜图、视觉相似性匹配 |
| 异常检测 | 识别与正常模式偏离的向量 |
| 去重 | 基于语义的近似重复检测 |
1.3 主流向量数据库对比¶
| 特性 | Milvus | Pinecone | Chroma | Qdrant | Weaviate |
|---|---|---|---|---|---|
| 开源 | ✅ Apache 2.0 | ❌ 商业 SaaS | ✅ Apache 2.0 | ✅ Apache 2.0 | ✅ BSD-3 |
| 部署方式 | 自托管 / Zilliz Cloud | 全托管 | 嵌入式 / 服务端 | 自托管 / Cloud | 自托管 / Cloud |
| 最大规模 | 十亿级 | 十亿级 | 百万级 | 十亿级 | 十亿级 |
| 语言 SDK | Python/Java/Go/Node | Python/Node/Go | Python/JS | Python/Rust/Go | Python/JS/Go/Java |
| 混合搜索 | ✅ | ✅ | ✅ 基础 | ✅ | ✅ |
| 标量过滤 | ✅ 高级 | ✅ | ✅ 基础 | ✅ 高级 | ✅ 高级 |
| 多租户 | ✅ | ✅ | ❌ | ✅ | ✅ |
| GPU 加速 | ✅ | N/A | ❌ | ❌ | ❌ |
| 适用场景 | 大规模生产环境 | 快速上手、无运维 | 原型/小项目 | 中大规模、Rust 高性能 | GraphQL 原生、多模态 |
1.4 选型建议¶
快速原型 / 本地开发 → Chroma(嵌入式,零配置)
中小规模生产 → Qdrant(性能好,资源占用低)
大规模生产 → Milvus(成熟稳定,功能全面)
无运维需求 → Pinecone(全托管 SaaS)
GraphQL / 多模态 → Weaviate(对象存储 + 向量)
2. 向量索引算法¶
2.1 暴力搜索(Flat / Brute-Force)¶
最简单的方式,计算查询向量与所有向量的距离,返回最近的 K 个。
- 时间复杂度:\(O(n \cdot d)\),n 为向量数,d 为维度
- 优点:100% 精确召回
- 缺点:规模大时不可接受
- 适用:小数据集(< 10 万)或作为基准对比
2.2 IVF(Inverted File Index)¶
将向量空间用 K-Means 聚类划分为多个 Voronoi 单元,查询时只搜索最近的几个单元。
训练阶段:
1. 对所有向量执行 K-Means 聚类 → nlist 个聚类中心
2. 每个向量分配到最近的聚类
查询阶段:
1. 找到查询向量最近的 nprobe 个聚类中心
2. 只在这 nprobe 个聚类内搜索
关键参数: - nlist:聚类数量(通常 \(\sqrt{n}\) 到 \(4\sqrt{n}\)) - nprobe:查询时搜索的聚类数(越大越精确,越慢)
2.3 HNSW(Hierarchical Navigable Small World)¶
基于跳表 + 小世界图的层次化数据结构,是目前最流行的 ANN 算法之一。
构建阶段:
Layer 3: [A] ------------- [D] (稀疏,长距离跳转)
Layer 2: [A] ----- [C] --- [D]
Layer 1: [A] - [B] - [C] - [D] - [E]
Layer 0: [A]-[B]-[C]-[D]-[E]-[F]-[G]-[H] (稠密,所有节点)
查询阶段:
1. 从最高层入口节点开始
2. 在当前层贪心搜索最近邻
3. 下降到下一层,重复搜索
4. 在最底层返回 Top-K 结果
关键参数: - M:每个节点的最大边数(通常 16-64) - ef_construction:构建时的候选列表大小(越大索引质量越高) - ef_search:查询时的候选列表大小(越大召回越高)
优缺点: - ✅ 查询速度极快(对数复杂度) - ✅ 召回率高(通常 > 95%) - ❌ 内存占用大(需存储图结构) - ❌ 构建速度相对较慢
2.4 PQ(Product Quantization)¶
通过向量压缩大幅降低内存开销,适合超大规模场景。
原理:
1. 将 D 维向量切分为 m 个子向量
2. 对每个子空间独立执行 K-Means(通常 K=256)
3. 每个子向量用 1 字节编码(聚类 ID)
4. 原始向量 D×4 bytes → m bytes
示例(D=128,m=8):
原始:128 × 4 = 512 bytes
压缩:8 bytes(压缩比 64x)
2.5 常用索引组合¶
| 索引类型 | 内存占用 | 查询速度 | 召回率 | 适用规模 |
|---|---|---|---|---|
| Flat | 高 | 慢 | 100% | < 10 万 |
| IVF_Flat | 高 | 快 | 95%+ | 10-1000 万 |
| HNSW | 很高 | 很快 | 98%+ | 10-5000 万 |
| IVF_PQ | 低 | 快 | 90%+ | 1000 万-10 亿 |
| IVF_HNSW | 高 | 很快 | 97%+ | 100-5000 万 |
| HNSW_PQ | 中 | 很快 | 93%+ | 5000 万-10 亿 |
3. Milvus 实战¶
3.1 安装与启动¶
# Docker Compose 方式(推荐)
wget https://github.com/milvus-io/milvus/releases/download/v2.6.11/milvus-standalone-docker-compose.yml -O docker-compose.yml
docker compose up -d
# 验证启动
docker compose ps
# 安装 Python SDK
pip install pymilvus==2.6.11
3.2 连接与建集合¶
from pymilvus import (
connections, Collection, FieldSchema,
CollectionSchema, DataType, utility
)
# 连接 Milvus
connections.connect("default", host="localhost", port="19530")
# 定义字段
fields = [
FieldSchema(name="id", dtype=DataType.INT64,
is_primary=True, auto_id=True),
FieldSchema(name="title", dtype=DataType.VARCHAR, max_length=512),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=768),
]
# 创建集合
schema = CollectionSchema(fields, description="文档向量集合")
collection = Collection("documents", schema)
print(f"集合创建成功: {collection.name}")
3.3 创建索引¶
# 创建 HNSW 索引
index_params = {
"index_type": "HNSW",
"metric_type": "COSINE", # 余弦相似度
"params": {
"M": 32, # 每个节点的边数
"efConstruction": 256 # 构建质量
}
}
collection.create_index("embedding", index_params)
print("索引创建完成")
# 加载到内存
collection.load()
3.4 插入数据¶
import numpy as np
from sentence_transformers import SentenceTransformer
# 加载 Embedding 模型
model = SentenceTransformer("BAAI/bge-base-zh-v1.5")
# 准备数据
documents = [
"向量数据库是 AI 时代的关键基础设施",
"Milvus 支持十亿级向量的高效检索",
"HNSW 算法在召回率和速度之间取得了良好平衡",
"RAG 系统将检索与生成相结合",
"Product Quantization 可以大幅压缩向量存储",
]
# 生成 Embedding
embeddings = model.encode(documents).tolist()
# 插入数据
data = [documents, embeddings]
result = collection.insert(data)
print(f"插入 {result.insert_count} 条记录")
# 刷新确保数据持久化
collection.flush()
3.5 向量搜索¶
# 查询向量
query = "如何优化大规模向量检索?"
query_embedding = model.encode([query]).tolist()
# 执行搜索
search_params = {
"metric_type": "COSINE",
"params": {"ef": 128} # HNSW 搜索参数
}
results = collection.search(
data=query_embedding,
anns_field="embedding",
param=search_params,
limit=3,
output_fields=["title"]
)
# 输出结果
for hits in results:
for hit in hits:
print(f"ID: {hit.id}, 距离: {hit.distance:.4f}, "
f"标题: {hit.entity.get('title')}")
3.6 混合查询(标量过滤 + 向量搜索)¶
# 带过滤条件的搜索
results = collection.search(
data=query_embedding,
anns_field="embedding",
param=search_params,
limit=5,
expr='category == "tech" and publish_year >= 2024',
output_fields=["title", "category", "publish_year"]
)
3.7 删除与清理¶
# 按条件删除
collection.delete('id in [1, 2, 3]')
# 删除集合
utility.drop_collection("documents")
# 断开连接
connections.disconnect("default")
4. RAG 系统中的向量数据库集成¶
4.1 RAG 架构概览¶
┌─────────────────────────────────────────────┐
│ RAG Pipeline │
├──────────────────┬──────────────────────────┤
│ 离线索引阶段 │ 在线查询阶段 │
│ │ │
│ 文档 → 分块 │ 用户问题 │
│ ↓ │ ↓ │
│ Embedding 编码 │ Embedding 编码 │
│ ↓ │ ↓ │
│ 存入向量数据库 │ 向量数据库检索 Top-K │
│ │ ↓ │
│ │ 拼接 Prompt + 检索结果 │
│ │ ↓ │
│ │ LLM 生成回答 │
└──────────────────┴──────────────────────────┘
4.2 完整 RAG 示例(LangChain + Milvus)¶
from langchain_community.vectorstores import Milvus
from langchain_community.embeddings import HuggingFaceBgeEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_openai import ChatOpenAI
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
# 1. 加载文档
loader = TextLoader("knowledge_base.txt", encoding="utf-8")
documents = loader.load()
# 2. 文档分块
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ".", " "]
)
chunks = splitter.split_documents(documents)
print(f"文档分为 {len(chunks)} 个块")
# 3. Embedding + 存入 Milvus
embeddings = HuggingFaceBgeEmbeddings(
model_name="BAAI/bge-base-zh-v1.5"
)
vectorstore = Milvus.from_documents(
documents=chunks,
embedding=embeddings,
connection_args={"host": "localhost", "port": "19530"},
collection_name="rag_knowledge",
drop_old=True
)
# 4. 构建 RAG 链(LCEL,替代已废弃的 RetrievalQA)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
system_prompt = """基于以下上下文回答问题。如果上下文中没有相关信息,请说"我不知道"。
{context}"""
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
("human", "{input}")
])
question_answer_chain = create_stuff_documents_chain(llm, prompt)
rag_chain = create_retrieval_chain(retriever, question_answer_chain)
# 5. 查询
response = rag_chain.invoke({"input": "向量数据库的核心优势是什么?"})
print(response["answer"])
for doc in response["context"]:
print(f" 来源: {doc.metadata.get('source', 'N/A')}")
4.3 分块策略对比¶
| 策略 | 块大小 | 适用场景 | 注意事项 |
|---|---|---|---|
| 固定大小 | 200-500 tokens | 通用 | 可能切断语义 |
| 递归分割 | 500-1000 chars | 结构化文档 | 优先在段落边界切分 |
| 语义分割 | 动态 | 高质量需求 | 需额外模型,速度慢 |
| 按句子 | 1-3 句 | 问答场景 | 块小,检索噪音大 |
| 父子分块 | 分层 | 长文档 | 检索小块,返回父块上下文 |
5. 性能优化¶
5.1 分片与部署¶
# Milvus 分布式部署(Kubernetes Helm)
# values.yaml 关键配置
cluster:
enabled: true
queryNode:
replicas: 3 # 查询节点副本数
resources:
limits:
memory: "16Gi"
cpu: "8"
dataNode:
replicas: 2
proxy:
replicas: 2
minio: # 对象存储
mode: distributed
replicas: 4
etcd: # 元数据存储
replicaCount: 3
5.2 索引调优指南¶
# 场景1:高召回率优先(推荐系统)
index_params_high_recall = {
"index_type": "HNSW",
"metric_type": "COSINE",
"params": {"M": 64, "efConstruction": 512}
}
search_params_high_recall = {"ef": 256}
# 场景2:低延迟优先(实时搜索)
index_params_low_latency = {
"index_type": "IVF_FLAT",
"metric_type": "L2",
"params": {"nlist": 2048}
}
search_params_low_latency = {"nprobe": 16}
# 场景3:大规模低成本(十亿级)
index_params_large_scale = {
"index_type": "IVF_PQ",
"metric_type": "L2",
"params": {"nlist": 4096, "m": 16, "nbits": 8}
}
search_params_large_scale = {"nprobe": 64}
5.3 混合检索(向量 + 关键词)¶
from pymilvus import AnnSearchRequest, RRFRanker
# 向量搜索请求
vector_req = AnnSearchRequest(
data=query_embedding,
anns_field="dense_embedding",
param={"metric_type": "COSINE", "params": {"ef": 128}},
limit=20
)
# 稀疏向量搜索(BM25 / SPLADE)
sparse_req = AnnSearchRequest(
data=sparse_query_embedding,
anns_field="sparse_embedding",
param={"metric_type": "IP"},
limit=20
)
# RRF 融合排序
results = collection.hybrid_search(
reqs=[vector_req, sparse_req],
ranker=RRFRanker(k=60),
limit=10,
output_fields=["title", "content"]
)
5.4 性能优化清单¶
| 优化项 | 措施 | 预期效果 |
|---|---|---|
| Embedding 模型 | 选择合适维度(384/768 vs 1536) | 降低存储和计算开销 |
| 批量插入 | 每批 1000-10000 条 | 提升写入吞吐 5-10x |
| 索引预热 | 查询前调用 collection.load() | 避免首次查询延迟 |
| 分区键 | 按 tenant_id 分区 | 缩小搜索范围 |
| 标量索引 | 对过滤字段创建索引 | 加速混合查询 |
| 连接池 | 复用 Milvus 连接 | 减少连接开销 |
| 结果缓存 | Redis 缓存热门查询 | 减少重复计算 |
6. 面试高频题¶
Q1: 向量数据库和传统数据库的核心区别?¶
传统数据库基于精确匹配(B-Tree/Hash),适合结构化查询;向量数据库基于近似最近邻搜索(ANN),适合语义相似性检索。核心区别在于查询语义:精确 vs 近似、关键词 vs 语义。
Q2: HNSW 的基本原理和时间复杂度?¶
HNSW 构建一个层次化的小世界图。构建时间 \(O(n \log n)\),查询时间 \(O(\log n)\)。通过从高层(稀疏长距离连接)逐层下降到底层(稠密短距离连接)实现高效搜索。
Q3: 如何选择向量索引类型?¶
- 小数据 (< 10万):Flat(精确搜索)
- 高召回需求:HNSW(内存允许时首选)
- 大规模低成本:IVF_PQ(内存受限)
- 平衡方案:IVF_FLAT 或 IVF_HNSW
Q4: RAG 系统中如何优化检索质量?¶
- 选择合适的分块策略和大小
- 使用高质量 Embedding 模型(如 BGE/GTE 系列)
- 混合检索(稠密 + 稀疏向量)
- 重排序(Cross-Encoder Reranker)
- 多路召回 + 融合排序(RRF)
Q5: Milvus 如何实现水平扩展?¶
Milvus 采用存储-计算分离架构:Proxy 负责路由,QueryNode 负责查询(可水平扩展),DataNode 负责写入,底层使用 MinIO/S3 做对象存储、etcd 做元数据管理。通过增加 QueryNode 副本数线性提升查询吞吐。
Q6: PQ 量化会损失多少精度?如何弥补?¶
PQ 通常导致 5-10% 的召回率下降。弥补方法: - 增加
nprobe(搜索更多聚类) - 使用 OPQ(Optimized PQ)旋转优化 - 结合 Refine 步骤(用原始向量重排 Top-K) - IVF_PQ + HNSW_PQ 组合索引
✅ 检查清单¶
- 理解向量数据库的核心概念和使用场景
- 能对比 Milvus/Pinecone/Chroma/Qdrant/Weaviate 的优劣势
- 掌握 HNSW、IVF、PQ 三种核心索引算法的原理
- 能用 Milvus Python SDK 完成建表、插入、搜索全流程
- 理解 RAG 系统中向量数据库的角色与集成方式
- 能根据场景选择合适的索引类型和参数
- 了解混合检索(稠密 + 稀疏)的实现方式
- 掌握分布式部署和性能调优的基本策略
- 能回答面试中关于向量数据库的常见问题