向量数据库

2025年8月26日 · 4395

学习文档地址:第三章 第三节 向量数据库

什么是向量数据库

在上一章,我们了解了向量嵌入的基础概念,即将多模态的图像、文本等进行向量化,成为多维向量数据。而向量数据库,可以提供对这些向量数据的存储和查询功能。毕竟在 RAG 中,我们并不能像 MySQL 那样通过where id = 3去查询数据,只能通过相似性去查找。

  1. 高效的相似性搜索:这是向量数据库最重要的功能。它利用专门的索引技术(如 HNSW, IVF),能够在数十亿级别的向量中实现毫秒级的近似最近邻(ANN)查询,快速找到与给定查询最相似的数据。
  2. 高维数据存储与管理:专门为存储高维向量(通常维度成百上千)而优化,支持对向量数据进行增、删、改、查等基本操作。
  3. 丰富的查询能力:除了基本的相似性搜索,还支持按标量字段过滤查询(例如,在搜索相似图片的同时,指定年份 > 2023)、范围查询和聚类分析等,满足复杂业务需求。
  4. 可扩展与高可用:现代向量数据库通常采用分布式架构,具备良好的水平扩展能力和容错性,能够通过增加节点来应对数据量的增长,并确保服务的稳定可靠。
  5. 数据与模型生态集成:与主流的 AI 框架(如 LangChain, LlamaIndex)和机器学习工作流无缝集成,简化了从模型训练到向量检索的应用开发流程。

工作原理

向量数据库通常采用四层架构,通过以下技术手段实现高效相似性搜索:

  1. 存储层:存储向量数据和元数据,优化存储效率,支持分布式存储
  2. 索引层:维护索引算法(HNSW、LSH、PQ等),创建和优化索引,支持索引调整
  3. 查询层:处理查询请求,支持混合查询,实现查询优化
  4. 服务层:管理客户端连接,提供监控和日志,实现安全管理

主流向量数据库

当前一些主流的向量数据库

数据库 (Database)类型核心特点与优势主要应用场景
Milvus / Zilliz开源分布式系统 / 云服务功能最全面、生态最成熟的开源标准。- 为大规模设计:纯粹的分布式架构,支持水平扩展,能处理百亿甚至千亿级别的向量。- 高度可配置:支持多种索引类型(如HNSW, IVFFlat)和距离度量,可根据场景调优。- 企业级特性:支持多租户、分区、TTL等高级功能。- Zilliz Cloud提供全托管服务,由原厂打造。大规模、生产级的AI应用。需要处理海量向量、对性能和可扩展性有极高要求的企业级搜索引擎、推荐系统、AI安全等。
Pinecone商业闭源云服务 (SaaS)简单易用、性能卓越的全托管服务。- 开箱即用:完全的SaaS模式,无需任何运维,开发者体验极佳。- 性能稳定:以低延迟和高召回率著称,针对实时应用做了深度优化。- 无服务器架构 (Serverless):根据使用量自动伸缩,无需关心底层资源。- 提供了命名空间(Namespace)等方便多租户管理的工具。快速开发和迭代的AI原生应用。希望将精力完全集中在应用逻辑上,不想处理任何数据库运维的初创公司和企业团队。
Weaviate开源 / 云服务内置数据处理与GraphQL API。- 开箱即用的模块化:内置了与OpenAI, Cohere等模型的集成模块,可以在数据库内部完成数据的向量化。- GraphQL API:提供强大的GraphQL查询接口,便于进行复杂的数据关联查询。- 混合搜索:原生支持关键词搜索和向量搜索的混合,非常实用。需要将向量搜索与结构化数据过滤紧密结合的场景。希望在数据库层面解决更多数据处理问题的应用。
Qdrant开源 / 云服务性能与内存安全。- Rust语言编写:提供了内存安全保证和出色的性能。- 高级过滤与量化:支持丰富的元数据过滤条件,并且提供了先进的标量量化和二值量化技术,能在保持高精度的同时,显著降低内存消耗和成本。- 分片与推荐系统:支持分片和集合推荐等高级功能。性能、资源效率和数据过滤能力有苛刻要求的场景。希望在有限的硬件资源下实现高性能检索的应用。
ChromaDB开源 / 嵌入式优先为AI开发者打造的“开发者友好”数据库。- 嵌入式优先:可以作为Python库直接在应用中运行,无需部署单独的服务,极大地简化了开发和原型验证。- API简单直观:专为AI工作流(特别是RAG)设计,API非常易于上手。- 分析功能:提供了一些内置的工具来分析和可视化你的嵌入数据。本地开发、快速原型验证、中小型RAG应用。作为Jupyter Notebook/Streamlit等应用的一部分,进行数据科学实验和构建Demo。
PGVectorPostgreSQL 扩展在现有关系型数据库中增加向量能力。- 生态成熟:可以完全利用PostgreSQL强大的生态系统,如事务、备份、权限管理、丰富的SQL功能等。- 数据统一:无需维护一个独立的数据库,可以将向量数据与业务的结构化数据存储在一起,方便进行联合查询。- 运维简单:对于已经在使用PostgreSQL的团队,学习和维护成本极低。已经在使用PostgreSQL,并希望快速、低成本地为现有应用增加向量搜索功能的场景。数据混合查询(例如,查找“过去30天内,由A部门创建的,内容与‘AI安全’相关的文档”)需求强烈的应用。

如何选择

以 FAISS 为例

from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_core.documents import Document

# 1. 示例文本和嵌入模型
texts = [
    "张三是法外狂徒",
    "FAISS是一个用于高效相似性搜索和密集向量聚类的库。",
    "LangChain是一个用于开发由语言模型驱动的应用程序的框架。"
]
docs = [Document(page_content=t) for t in texts]
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")

# 2. 创建向量存储并保存到本地
vectorstore = FAISS.from_documents(docs, embeddings)

local_faiss_path = "./faiss_index_store"
vectorstore.save_local(local_faiss_path)

print(f"FAISS index has been saved to {local_faiss_path}")

# 3. 加载索引并执行查询
# 加载时需指定相同的嵌入模型,并允许反序列化
loaded_vectorstore = FAISS.load_local(
    local_faiss_path,
    embeddings,
    allow_dangerous_deserialization=True
)

# 相似性搜索
query = "FAISS是做什么的?"
results = loaded_vectorstore.similarity_search(query, k=1)

print(f"\n查询: '{query}'")
print("相似度最高的文档:")
for doc in results:
    print(f"- {doc.page_content}")

练习

  1. LlamaIndex默认会将数据存储为透明可读的JSON格式,运行03_llamaindex_vector.py文件,查看保存的json文件内容。
JSON内容

  • default__vector_store.json - 核心向量存储
    • 存储文本节点的 512 维向量嵌入
    • 包含节点ID到向量的映射关系
    • 实际保存中文文本的数值化表示
  • docstore.json - 文档存储
    • 保存原始中文文本内容
    • 维护文档与节点的层级关系
    • 包含"张三是法外狂徒"等示例文本
  • index_store.json - 索引元数据
    • 存储索引配置信息
    • 记录节点存储位置映射
    • 提供索引重构的元信息
  • image__vector_store.json - 图像向量存储(当前为空)
    • 预留用于多模态检索
    • 可存储图像+文本的联合嵌入
  • graph_store.json - 图关系存储(当前为空)
    • 预留用于知识图谱扩展
    • 可存储文档间的实体关系
  1. 新建一个代码文件实现对LlamaIndex存储数据的加载和相似性搜索。
from llama_index.core import (
    Document,
    Settings,
    load_index_from_storage,
    StorageContext,
)
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

# 1. 配置全局嵌入模型
Settings.embed_model = HuggingFaceEmbedding("BAAI/bge-small-zh-v1.5")

# 2. 创建示例文档
texts = [
    "张三是法外狂徒",
    "LlamaIndex是一个用于构建和查询私有或领域特定数据的框架。",
    "它提供了数据连接、索引和查询接口等工具。",
]
docs = [Document(text=t) for t in texts]

# 3. 创建索引并持久化到本地
index = VectorStoreIndex.from_documents(docs)
persist_path = "./llamaindex_index_store"
index.storage_context.persist(persist_dir=persist_path)
print(f"LlamaIndex 索引已保存至: {persist_path}")

# 4. 加载索引
loaded_index = StorageContext.from_defaults(persist_dir=persist_path)
vector_idnex = load_index_from_storage(loaded_index)

# 5. 构建查询
# 设置LlamaIndex的配置,使用本地Ollama大模型
from llama_index.llms.ollama import Ollama

Settings.llm = Ollama(
    model="gemma3-4b:Q4_K_M", base_url="http://127.0.0.1:11434", request_timeout=150
)
query = "LlamaIndex是做什么的?"
query_engine = vector_idnex.as_query_engine()
response = query_engine.query(query)
print(f"\n查询: '{query}'")
print(f"回答: {response}")
输出结果

LlamaIndex 索引已保存至: ./llamaindex_index_store
Loading llama_index.core.storage.kvstore.simple_kvstore from ./llamaindex_index_store\docstore.json.
Loading llama_index.core.storage.kvstore.simple_kvstore from ./llamaindex_index_store\index_store.json.

查询: 'LlamaIndex是做什么的?'
回答: LlamaIndex用于构建和查询私有或领域特定数据。

Milvus数据库

Milvus 是一个开源的、专为大规模向量相似性搜索和分析而设计的向量数据库,专为生产环境而设计。

版本对比

版本部署模式架构核心优势核心劣势最佳适用场景
Milvus Standalone自托管单机单进程极其简单,快速启动无高可用,扩展性有限学习、开发、原型验证(MVP)、小型应用
Milvus Cluster自托管分布式微服务高性能,高可用,可处理海量数据部署运维复杂,资源成本高大规模、数据私有化的生产环境
Zilliz Cloud全托管分布式云原生完全免运维,Serverless,快速上线依赖云服务,长期成本可能更高追求开发速度、希望外包运维的各类生产应用

基本概念

Milvus 概念传统数据库 (如 MySQL) 类比解释
Collection (集合)Table (表)一个 Collection 就是一张数据表,是数据管理的顶层容器。例如,你可以创建一个名为 rag_documents 的 Collection 来存储所有用于RAG的文档块。
Partition (分区)Partition (分区)这是一个可选的物理存储概念。你可以将一个 Collection 划分为多个 Partition,查询时可以指定只在某个分区内搜索,这能有效隔离数据并提升查询性能。例如,可以按书籍名称创建分区。
Schema (模式)Table Definition / DDL (表结构定义)Schema 定义了一个 Collection 中包含哪些“列”(在Milvus中称为 Field),以及每一列的数据类型和约束。创建 Collection 时必须先定义好 Schema
Entity (实体)Row (行)一个 Entity 就是表中的一行数据。它包含了 Schema 中定义的所有 Field 的具体值。在RAG场景中,一个 Entity 通常就代表一个文本块(Chunk)
Field (字段)Column (列)Field 就是表中的一列。一个 Schema 由多个 Field 组成。例如,chunk_id, chunk_text, chunk_vector 都可以是 Field。
Alias(别名)类似于View,但有本质区别Alias 是一个指向某个特定 Collection虚拟指针或昵称。能在无需修改代码和重启服务器的基础上,无缝升级数据。

Collection

在 Milvus 上,可以创建多个 Collections 来管理数据,并将数据作为实体插入到 Collections 中。Collections 和实体类似于关系数据库中的表和记录。

一个 Collection Schema 有一个主键、最多四个向量字段和几个标量字段。下图说明了如何将文章映射到模式字段列表。

collection如何将文章映射到模式字段列表

Schema

Schema 定义了 Collections 的数据结构。

一个 Collection Schema 有一个主键最多四个向量字段和几个标量字段。下图说明了如何将文章映射到模式字段列表。

  • Article 是主键字段,每篇文章只有唯一且不重复的 ID
  • ImageEmbedding Summary Embedding SummarySpareEmbedding 是向量字段;
  • 其他信息属于标量字段,用于更好的检索;

如何将文章映射到模式字段列表

主键字段

在 Collections 中,每个实体的主键都应该是全局唯一的。添加主字段时,需要显式地将其数据类型设置为VARCHARINT64。将其数据类型设置为INT64表示主键应为整数,类似于12345 ;将其数据类型设置为VARCHAR表示主键应为字符串,类似于my_entity_1234 。

你也可以启用AutoID,让 Milvus 自动为进入的实体分配主键。在集合中启用AutoID后,插入实体时不要包含主键。

Collections 中的主字段没有默认值,也不能为空。

Partition

每个 Collection 在创建时都会有一个名为 _default 的默认分区。我们可以根据业务需求创建更多的分区,将数据按特定规则(如类别、日期等)存入不同分区。 一个 Collections 最多可以有 1024 个分区。

Alias

在 Milvus 中,别名是一个 Collection 的二级可变名称。使用别名提供了一个抽象层,使您可以在不修改应用程序代码的情况下动态切换 Collections。这对于生产环境中的无缝数据更新、A/B 测试和其他操作符特别有用。

别名的关键属性:

  • 一个 Collection 可以有多个别名。
  • 一个别名一次只能指向一个 Collections。
  • 处理请求时,Milvus 会首先检查是否存在提供名称的 Collection。如果不存在,它就会检查该名称是否是某个 Collection 的别名。

索引

📋
总结

MySQL 索引是“精确找行”,Milvus 索引是“模糊找相似”。

检索

类似于传统数据库的查询,但是有些本质区别。传统数据库的查询是基于精确匹配,而检索是基于语义的相似性

近似最近邻 (Approximate Nearest Neighbor, ANN) 检索

ANN 检索利用预先构建好的索引,能够极速地从海量数据中找到与查询向量最相似的 Top-K 个结果;

Milvus多模态实践

示例代码 all-in-rag/code/C3/04_multi_milvus.py

输出结果

我在图片库中又找了几张网图加进去,看整体效果

检索结果:
  Top 1: ID=460393204701700350, 距离=0.9466, 路径='../../data/C3\dragon\query.png'
  Top 2: ID=460393204701700344, 距离=0.7443, 路径='../../data/C3\dragon\dragon02.png'
  Top 3: ID=460393204701700348, 距离=0.6851, 路径='../../data/C3\dragon\dragon06.png'
  Top 4: ID=460393204701700341, 距离=0.6772, 路径='../../data/C3\dragon\dragon001.png'
  Top 5: ID=460393204701700343, 距离=0.6760, 路径='../../data/C3\dragon\dragon003.png'
  Top 6: ID=460393204701700342, 距离=0.6459, 路径='../../data/C3\dragon\dragon002.png'
  Top 7: ID=460393204701700345, 距离=0.6049, 路径='../../data/C3\dragon\dragon03.png'
  Top 8: ID=460393204701700347, 距离=0.5360, 路径='../../data/C3\dragon\dragon05.png'

第一张图就是query.png,但是从检索结果可以看到,它的距离也不是1,而是0.9466,这是因为,索引时的图片和检索时的图片处理有差异; 从代码可以看出,编码查询的时候除了输入图像,我们还输入了text查询文本,这样就会使得同一图片的检索距离并不为1。如果去掉这个一条龙的描述,则距离结果就是1了;

# 编码查询
def encode_query(self, image_path: str, text: str) -> list[float]:
	with torch.no_grad():
		query_emb = self.model.encode(image=image_path, text=text)
	return query_emb.tolist()[0]

# 编码图像
def encode_image(self, image_path: str) -> list[float]:
	with torch.no_grad():
		query_emb = self.model.encode(image=image_path)
	return query_emb.tolist()[0]
	
query_text = "一条龙"
# 查询编码使用的是图片+文本
query_vector = encoder.encode_query(image_path=query_image_path, text=query_text)
# 如果直接采用图片,不使用文本,则会显示距离为1
# query_vector = encoder.encode_image(image_path=query_image_path)
  • 查询编码时不加入文字的结果
检索结果:
  Top 1: ID=460393204701700360, 距离=1.0000, 路径='../../data/C3\dragon\query.png'
  Top 2: ID=460393204701700354, 距离=0.7908, 路径='../../data/C3\dragon\dragon02.png'
  Top 3: ID=460393204701700351, 距离=0.7321, 路径='../../data/C3\dragon\dragon001.png'
  Top 4: ID=460393204701700358, 距离=0.6975, 路径='../../data/C3\dragon\dragon06.png'
  Top 5: ID=460393204701700353, 距离=0.6867, 路径='../../data/C3\dragon\dragon003.png'
  Top 6: ID=460393204701700355, 距离=0.6592, 路径='../../data/C3\dragon\dragon03.png'
  Top 7: ID=460393204701700352, 距离=0.6551, 路径='../../data/C3\dragon\dragon002.png'
  Top 8: ID=460393204701700357, 距离=0.5703, 路径='../../data/C3\dragon\dragon05.png'

Milvus的多模态查询结果-不同龙的检索情况

讨论