news 2026/9/3 22:35:02

向量数据库入门:语义搜索与RAG应用实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
向量数据库入门:语义搜索与RAG应用实践

向量数据库在最近几年成了检索类应用的基础组件,尤其是 RAG(检索增强生成)类项目出现之后,几乎每个做 AI 应用的团队都会评估一次“要不要引入向量数据库”。很多开发者的第一反应是:传统数据库已经支持模糊查询、全文索引,为什么还要再引入一个专门的存储系统?答案在于:向量数据库解决的不是“关键词匹配”,而是“语义相似度检索”。本文围绕向量数据库的入门路径展开,从核心概念、相似度计算、主流工具选型,到一个可运行的最小案例,最后补充常见的故障场景和工程落地点。读完以后,你可以独立完成一个“文档语义搜索”的本地实验,并对生产环境会遇到的索引、持久化、过滤和选型问题建立基本判断。

1. 先理解为什么需要向量数据库

1.1 传统数据库很难做语义搜索

传统关系型数据库使用 SQL 查询,核心匹配方式是等值、范围、LIKE 和全文索引。LIKE 只能找“字面上相同”的内容,全文索引虽然引入了分词和倒排,但本质上仍然是关键词匹配。用户搜“怎么换门锁”,数据库无法判断“门锁打不开”“锁芯卡住了”“智能锁没反应”这些句子是否和用户问题相关,除非这些句子恰好包含同一个词。

这种场景在电商搜索、客服问答、文档知识库、推荐系统中非常常见。传统的解决方式有两种:一种是人工维护同义词表,另一种是训练文本分类模型。前者维护成本高,后者只能覆盖有限的分类维度,都无法做到对任意一段文本进行实时相关性判断。

1.2 向量和 Embedding 是什么

向量可以简单理解为一串浮点数,比如[0.12, -0.34, 0.56, ...]。如果一段文本、一张图片或一条用户行为记录被模型转换成向量,那么在理想情况下,语义相近的内容会映射到向量空间中距离相近的位置。

“门锁坏了”和“锁打不开了”可能分别被转换成不同的向量,但在向量空间里的距离非常接近。这就是 Embedding 模型的作用:把高维的语义信息压缩成一个固定维度的数值向量。常用的模型包括all-MiniLM-L6-v2bge-large-zh、OpenAI 的text-embedding-3-small等。

向量数据库最基础的职责就是:存储这些向量,并在给定一个查询向量时,快速返回距离最近的 K 条记录。

1.3 向量数据库解决的核心问题

传统数据库也可以存向量字段,比如用JSON类型保存一个数组,但问题在于查询时无法高效地做相似度排序。从一个有千万行的表里扫描所有向量并逐一计算距离,在性能上是不可接受的。

向量数据库的核心能力有四点:

  1. 高效索引:使用 HNSW、IVF 等近似最近邻(ANN)索引,把检索范围压缩到一个很小的候选集合。
  2. 方便存储:每个向量可以附带元数据,查询时可以按标签过滤。
  3. 增量写入和更新:业务系统可以持续写入新的向量数据,而不需要重建整个库。
  4. 与 AI 链路集成:很多向量数据库原生支持 embedding 模型,写入文本时自动完成向量化。

一句话概括:传统数据库回答“哪个字段等于什么”,向量数据库回答“哪条记录和输入内容最相似”。

2. 核心概念:从 Collection 到相似度度量

2.1 最常用的几个抽象名词

不同向量数据库的术语略有差异,但核心概念基本一致。以 Chroma 为例:

名词类比传统数据库说明
Collection一组向量的集合,通常按业务场景划分,例如news_docsuser_profiles
Vector记录的一列一段文本或对象经过 Embedding 模型转换出的浮点数数组
Metadata普通字段附加在向量上的键值对,例如分类、作者、时间
ID主键每条向量记录的唯一标识
Query查询给一个查询向量,返回最相似的 K 条记录

对一个 Collection 的写入过程通常是这样:先确定一条记录的 ID,再写入 metadata,最后写入向量。如果使用默认 embedding function,可以直接传文本,由向量数据库内部完成向量化。

2.2 相似度度量:内积、余弦距离和欧氏距离

向量数据库判定“相似”靠的是距离函数。最常用的有三种:

  • 内积(Inner Product,IP):适合浅层语义匹配,特别是当向量已经归一化时。内积值越大表示越相似。
  • 余弦距离(Cosine):计算两个向量的夹角余弦值,适合文本语义相似度。它只关心方向,不关心长度,因此对不同长度的文本比较友好。
  • 欧氏距离(L2):计算两个向量的直线距离,值越小越相似。适合向量长度本身有业务含义的场景。

实际选哪种,要看你使用的 embedding 模型。有很多模型在训练时已经统一做了归一化,这时内积和余弦距离在排序结果上基本等价。建议先参考模型文档,没有明确说明时默认使用余弦距离。

2.3 ANN 索引为什么重要

假设有 1000 万条向量,每条 384 维。如果每一次查询都做全量暴力计算,大约需要进行 384 亿次浮点运算,单机响应时间会非常难看。

近似最近邻索引的思路是:不保证返回全局最优结果,但保证在极短时间内返回“接近最优”的结果。常用索引包括:

索引类型核心思想优点适用场景
HNSW多层跳表结构 + 图遍历查询快、召回高大多数业务场景首选
IVF聚类 + 倒排内存占用可控数据量大、对召回要求不极端
PQ向量压缩省内存海量向量、内存有限

入门阶段不需要深入推导索引原理,但需要记住一个事实:相似度检索结果是近似结果。某些训练好的模型在不同索引下可能返回不同的 K 条数据,这是正常现象。

2.4 主流向量数据库有哪些

热词中提到的 Chroma、Milvus、pgvector、Qdrant 是当前最常被讨论的四个方向。它们不是同一个层级的产品,选型时要先想清楚使用场景:

产品运行方式适合阶段主要特征
Chroma嵌入式本地库 / 服务端学习、原型、中小规模安装简单,Python API 友好,支持默认 embedding
Qdrant服务端中小规模生产Rust 实现,性能好,过滤功能强大
Milvus分布式服务端大规模生产适合大数据量、高并发,依赖 etcd、对象存储等组件
pgvectorPostgreSQL 扩展已有 PostgreSQL 场景不需要额外数据库,SQL 语法可复用

这四个方向在工程上都有大量实践。初学者不建议一开始就扑向分布式方案,先通过嵌入式数据库跑通流程,理解向量检索的基本链路,再去评估生产级组件会轻松很多。

3. 环境准备:用 Chroma 跑通第一步

3.1 为什么入门阶段选 Chroma

Chroma 是一个面向 AI 应用设计的嵌入式向量数据库,安装和启动成本很低。它提供了 Python SDK,甚至在本地不启动独立服务也能运行,非常适合用来学习向量检索流程。

它默认包含一个 embedding 函数,可以直接把文本传入add方法,由数据库内部完成向量化。这样你第一步可以跳过选择模型的复杂环节,先看到“文本进、相似结果出”的完整链路,之后再替换成自己的 embedding 模型。

要注意的是,Chroma 内部默认模型在不同版本中有变化。常见的是基于 MiniLM 的 ONNX 模型,首次运行时会从网络下载模型文件。如果公司网络受限,可能会卡在第一次写入。

3.2 环境要求和安装命令

建议环境如下:

项目建议
操作系统macOS / Linux / Windows 均可
Python3.9 及以上,具体以官方要求为准
pip最新版本
推荐工具Jupyter Notebook 或 VS Code

安装命令:

pip install chromadb

安装完成后,可以先执行一个简短检查:

import chromadb print(chromadb.__version__)

如果能正常输出版本号,说明安装成功。这里不需要额外安装数据库服务,Chroma 的客户端在本地运行时会自动创建数据目录。

3.3 第一个 Collection

打开一个新文件,写入以下代码:

import chromadb client = chromadb.PersistentClient(path="./chroma_demo") collection = client.get_or_create_collection( name="news_articles", metadata={"hnsw:space": "cosine"} ) print(collection.count())

解释一下关键部分:

  • PersistentClient(path="./chroma_demo"):指定数据持久化目录,后续重启进程数据仍然存在。如果不传路径,Chroma 默认使用内存模式,进程退出后数据丢失。
  • get_or_create_collection:如果集合不存在则创建,存在则直接获取,避免重复创建报错。
  • metadata={"hnsw:space": "cosine"}:指定向量距离计算方式为余弦距离。这个参数定义在 HNSW 索引的命名空间下,实际由底层索引读取。

运行后输出0,表示这是一个空集合。

4. 最小可运行案例:文档语义搜索

4.1 需求拆解

在正式写代码前,先明确这个最小案例要完成什么任务:

  1. 准备几条简单的客服问题文本。
  2. 写入向量数据库,附带分类元数据。
  3. 输入一条新问题,返回最相似的已有问题。
  4. 通过元数据过滤,只搜索某个分类下的相似记录。

这个需求是“文档语义搜索”的最简版本,覆盖了写入、查询、过滤三个核心动作。

4.2 演示数据说明

假设有一个客服知识库,里面存了几条历史工单标题:

docs = [ "手机无法开机", "门锁卡住打不开", "电脑蓝屏报错", "门禁卡刷不了", "路由器无法上网", "空调制冷效果差" ]

这些文本可以直接交给 Chroma 默认 embedding 函数处理。实际项目中,这些文本可能是一篇文档、一段 FAQ 或一条工单记录。

4.3 写入数据

继续使用之前的collection变量,执行写入:

ids = [str(i) for i in range(len(docs))] metadatas = [ {"category": "hardware"}, {"category": "smart_lock"}, {"category": "pc"}, {"category": "access_control"}, {"category": "network"}, {"category": "appliance"} ] collection.add( ids=ids, documents=docs, metadatas=metadatas ) print(collection.count())

这里最关键的是documents参数:当你不显式传入embeddings时,Chroma 会自动调用内置 embedding 函数,先将每段文本变成向量,再写入集合。

元数据和主键不是必选项,但强烈建议写。因为真实项目里每个向量都要关联业务主键,否则搜到了结果却不知道对应哪条业务记录,查询就没有落地价值。

4.4 查询相似文本

写入完成后,执行一次最基础的相似度查询:

results = collection.query( query_texts=["指纹锁识别失败"], n_results=3 ) for idx, doc in enumerate(results["documents"][0]): print(idx, doc)

查询文本是“指纹锁识别失败”,它和文档中的“门锁卡住打不开”“门禁卡刷不了”在语义上比较接近,但和“路由器无法上网”可能相关性较低。n_results=3表示返回最相似的 3 条。

可以看到,这里不需要分词,不需要写 SQL,也不需要人工维护关键词。查询过程完全由向量数据库根据语义距离完成。

4.5 带元数据过滤的查询

如果只想在“门锁”相关分类里搜索,可以把元数据过滤条件加进去:

filtered = collection.query( query_texts=["指纹锁识别失败"], n_results=3, where={"category": "smart_lock"} ) print(filtered["documents"])

where参数是元数据过滤条件,它的表现比通常想象的更强大一点:它是一个字典结构,可以支持简单的等于判断,某些版本还支持$and$or等组合条件。具体使用方式要参考对应版本的文档。

4.6 完整代码

把上面步骤合并成一个文件:

import chromadb client = chromadb.PersistentClient(path="./chroma_demo") collection = client.get_or_create_collection( name="news_articles", metadata={"hnsw:space": "cosine"} ) docs = [ "手机无法开机", "门锁卡住打不开", "电脑蓝屏报错", "门禁卡刷不了", "路由器无法上网", "空调制冷效果差" ] metadatas = [ {"category": "hardware"}, {"category": "smart_lock"}, {"category": "pc"}, {"category": "access_control"}, {"category": "network"}, {"category": "appliance"} ] collection.add( ids=[str(i) for i in range(len(docs))], documents=docs, metadatas=metadatas ) results = collection.query( query_texts=["指纹锁识别失败"], n_results=3 ) for idx, doc in enumerate(results["documents"][0]): print(idx, doc)

预期输出并不是固定的,取决于内置 embedding 模型的语义判断。通常“门锁卡住打不开”会排在第一或第二位,“门禁卡刷不了”也有较大概率出现在结果中。如果第一轮结果不符合直觉,先不要怀疑程序写错了,可以打印results["distances"]查看相似度分数,判断差距是否明显。

5. 关键细节:向量维度、持久化与参数含义

5.1 显式 embedding 和默认 embedding 的区别

前面代码中,我们没有手动生成向量。这样做的缺点是:看不到真正的向量长什么样,也不清楚维度是多少。

可以查看集合里的向量维度:

collection.get(ids=["0"])

返回结果中会有embeddings字段,里面是一个浮点数数组。Chroma 默认模型生成的向量通常是 384 维,这意味着每条文本最终被映射成了一个包含 384 个浮点数的数组。

显式生成向量的方式更贴近生产环境。生产项目中,文本向量通常由独立服务生成,再写入向量数据库,方便统一使用同一个模型:

from sentence_transformers import SentenceTransformer model = SentenceTransformer("all-MiniLM-L6-v2") embeddings = model.encode(docs).tolist() collection.add( ids=[str(i) for i in range(len(docs))], embeddings=embeddings, metadatas=metadatas )

查询时也要使用同一个模型生成查询向量:

query_embedding = model.encode(["指纹锁识别失败"]).tolist() results = collection.query( query_embeddings=query_embedding, n_results=3 )

关键原则有两条:

  1. 写入时使用什么模型,查询时也必须使用同一个模型,否则向量空间不一致,相似度没有意义。
  2. 如果 collection 在创建时已经绑定了 embedding 函数,add时既传documents又传embeddings,Chroma 会优先使用显式传入的向量。

5.2addupsert的使用边界

Chroma 的add方法在遇到重复 ID 时会报错。如果要更新已有记录,应该使用upsert

collection.upsert( ids=["0"], documents=["手机无法开机,尝试重启"], metadatas=[{"category": "hardware"}] )

upsert会覆盖已有 ID 的向量和元数据。如果没有该 ID,则插入新记录。实际项目里,文档更新是很常见的动作,建议对更新操作统一走upsert,避免报错中断任务。

5.3 查询返回字段的取舍

前面使用collection.query时没有指定返回字段。默认情况下 Chroma 会返回documentsmetadatasdistances等字段。如果你只需要业务主键,可以限制返回字段,减少网络和内存开销:

results = collection.query( query_texts=["指纹锁识别失败"], n_results=3, include=["documents", "distances"] )

include参数支持documentsmetadatasdistancesembeddings等。生产环境下建议按需选择,不要默认返回embeddings,因为向量对象通常很大,会拖慢响应速度。

5.4 为什么要配置持久化目录

Chroma 使用PersistentClient(path=...)时,数据会被存储在本地的 SQLite 和对应的索引文件中。这个设计对学习者非常友好:不需要启动 Docker,不需要配置网络,数据也能跨进程保留。

但要注意,这个能力只适合本地原型或中小规模场景。生产环境中如果把数据目录放在临时磁盘,服务重启后数据就会消失。需要将数据目录挂载到持久化存储,并做好备份机制。

5.5 初始参数设置常见错误

创建 collection 时最常见的错误是:

  • 遗忘metadata,导致默认距离度量不适合当前业务。
  • 使用一个已经存在的 collection,但希望修改距离度量。此时不会生效,因为集合的索引已按旧参数创建,需要删除重建。
  • 所有文本都使用同一个向量,导致查询结果全部等距。通常这是模型配置错误,不是数据库问题。

6. 从学习环境走向生产环境

6.1 本地 demo 与生产部署的差异

在学习阶段,一个 Chroma 本地库足以跑通流程。但进入生产环境后,有几道必答题:

关注点学习环境生产环境
数据持久化本地目录需要容量规划、备份和恢复
高可用单进程需要副本和故障切换
并发能力单客户端需要评估连接数和请求队列
向量模型默认模型需要独立模型服务,版本化管理
安全本地文件需要鉴权、网络隔离和数据加密
监控需要指标、日志和告警

如果你的项目一开始就没有打算引入独立中间件,可以评估 pgvector:它只是 PostgreSQL 的一个扩展,不需要额外维护一套服务,直接利用现有数据库能力。适合数据规模不大、团队不需要新运维组件的场景。

6.2 数据更新和索引重建

向量数据库写入本身很快,但重复更新大量数据时,索引会出现老化,导致查询性能下降。生产环境通常采用两类策略:

  1. 增量更新:业务发生时实时写入或更新单条记录,适合时效性要求高的场景。
  2. 批量重建:周期性全量重建集合,适合文档集合相对稳定的场景。

如果需要频繁批量更新,建议先写入到一个临时集合,再切换集合名称,避免在查询链路上执行耗时操作。切换的原子性由业务代码控制,向量数据库本身不一定提供跨集合的原子切换能力。

6.3 生产环境的最小保障清单

在把向量数据库接入生产前,至少确认下面几项:

  • 向量模型版本固定,代码里要显式记录模型的名称和版本。
  • 每条向量能通过 ID 关联到业务主键,方便回源查详情。
  • 对写入失败做补偿,避免部分文档写入失败导致检索不完整。
  • 对查询结果做后置过滤,因为向量检索是近似检索,偶尔会返回语义上不太相关的结果。
  • 监控查询耗时、写入耗时、集合大小、内存占用。
  • 如果使用独立部署的向量数据库,先评估容器镜像版本和客户端 SDK 版本的兼容性。

7. 常见问题:现象、原因和排查路径

7.1 查询结果明显不符合语义

现象:输入“指纹锁识别失败”,返回结果却和门锁毫无关系。
可能原因有三个:

  1. embedding 模型不适合中文,支持中文能力较弱。
  2. 写入和查询使用了不同的 embedding 模型。
  3. 数据量太少,语义相似度分数普遍较低,排序结果不稳定。

排查方式:

先查看results["distances"],判断分数分布。如果所有距离都很大且差距很小,通常是模型对文本区分度不足。可以换用更擅长中文的模型,例如bge-small-zh,或者使用商用 embedding 服务。

7.2 维度不匹配报错

现象:执行查询时出现 embedding dimension mismatch 相关错误。
原因:查询时传入的向量维度和集合内向量维度不一致。
排查方式:

打印集合内已有的向量维度,再打印查询向量的维度:

item = collection.get(ids=["0"]) print(len(item["embeddings"][0])) query_vec = model.encode(["测试"]).tolist() print(len(query_vec))

如果两者不一致,确认是否换了模型。注意:即使模型名称相同,不同版本也可能产生不同维度,因此生产环境必须固定模型版本。

7.3 数据重启后消失

现象:重新运行程序后collection.count()为 0。
原因:使用了默认的Client(),没有指定持久化路径。
排查方式:

核对创建客户端的方式,确认使用的是PersistentClient(path=...)。同时确认进程的工作目录是否一致,如果启动目录变了,相对路径会指向不同位置。

7.4 首次写入很慢,像卡住了

现象:执行collection.add时长时间没有返回。
原因:Chroma 内置 embedding 模型首次下载模型文件,网络较慢或网络受限。
解决办法:

  • 提前下载模型文件,放到缓存目录。
  • 改用显式 embedding 方式,先用独立模型生成向量,再写入 Chroma。
  • 使用Client()内存模式,不涉及持久化时能减少磁盘交互,但首次模型下载仍会发生。

7.5where条件不生效

现象:加了where后,返回结果中仍然包含其他分类的数据。
可能原因:

  • where字段名和写入时metadatas的键名不一致。
  • 当前版本不支持复杂的操作符组合。
  • 查询条件写在了错误的参数位置。

排查时应先打印collection.get(where={"category": "smart_lock"}),确认过滤是否在“不带相似度排序”的查询下生效。如果这里也查不到数据,说明是元数据写入问题,而不是查询问题。

8. 最佳实践与学习路径

8.1 写向量数据库代码时的四个习惯

第一,所有add操作都带上明确的 ID。很多向量数据库允许自动生成 ID,但如果后续要删除和更新,自动生成的 ID 很难与业务记录对应。

第二,写入和查询的 embedding 模型统一封装成一个函数。不要在多处直接调用模型,因为一旦替换模型,全链路都要同步调整。

第三,不要把查询结果直接当作最终答案。向量检索只是召回阶段,后续应该把命中的文本取出来,再通过排序、重排或规则过滤完成精排。

第四,数据规模增长后第一件要观察的事是延迟。如果查询耗时从毫秒级变成秒级,优先检查索引类型、数据量和资源限制。

8.2 向量数据库选型速查

根据业务阶段做初步判断:

项目阶段推荐选择理由
学习原理、写 demoChroma安装简单、API 友好
已有 PostgreSQL,数据量不大pgvector避免引入新组件
需要稳定服务和丰富过滤Qdrant单机部署简单,功能完善
海量数据、高并发、分布式需求Milvus面向生产级规模设计
使用云厂商生态各云厂商托管向量数据库运维成本低,但要注意厂商绑定

选型没有绝对好坏。最好先在一个小数据集上做基准测试,关注召回质量、写入速度、查询延迟、内存占用和运维成本。

8.3 下一步建议

完成本文的 demo 后,可以从三个方向深入:

  1. 替换成真实业务数据,加入更完善的元数据过滤和业务主键关联。
  2. 对比不同 embedding 模型在同一组数据上的查询效果,建立“数据、模型、距离函数”三者配合的体感。
  3. 将向量检索接入一个简单的问答流程:先用向量库检索相关文档,再把文档片段和用户问题一起交给大语言模型生成答案,这就是一个最简 RAG 原型。

向量数据库本身是一个工具,真正的难点在于:如何评估模型效果、如何组织数据、如何处理检索质量不稳定的情况。这些能力来自反复实验和对指标的敏感度。建议先从小数据量开始,把一条链路彻底跑通,再逐步扩展到更大的规模。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 22:29:20

Awesome Privacy 性能优化:让加密工具运行速度提升300%的技巧

Awesome Privacy 性能优化:让加密工具运行速度提升300%的技巧 你是否还在忍受加密工具加载缓慢、搜索卡顿的问题?当面对大量隐私服务数据时,等待时间过长不仅影响效率,更可能让用户放弃隐私保护的尝试。本文将从数据加载、搜索算…

作者头像 李华
网站建设 2026/9/3 22:27:58

小天鹅12公斤波轮洗衣机TB120M08DT值不值得买?选购与安装全解析

小天鹅 TB120M08DT 这类 12 公斤波轮洗衣机,值不值得买,不能只看“能不能洗”。如果家里人口多,预算又卡在两千元上下,不追求嵌入式橱柜和烘干功能,波轮确实是省心省力的选择。它不用弯腰、程序简单、洗涤时间短&#…

作者头像 李华
网站建设 2026/9/3 22:27:28

终极笔记协作指南:7款最佳开源macOS笔记应用推荐

终极笔记协作指南:7款最佳开源macOS笔记应用推荐 想要找到既免费又功能强大的笔记应用?开源macOS应用程序集合为你提供了完美的解决方案!🎯 这个精心整理的资源库汇集了617个开源应用程序,覆盖49个不同类别&#xff0…

作者头像 李华
网站建设 2026/9/3 22:26:48

AI研究者逃离大厂:个人如何搭建可迁移的AI研究基础设施

AI 领域的职业流动正在出现一个值得注意的信号:越来越多的顶尖研究者,开始用“离开大厂”或“拒绝入职大厂”的方式,重新定义自己的科研路径。不是简单的跳槽,而是直接脱离长期雇佣体系,转向独立实验室、个人研究项目&…

作者头像 李华
网站建设 2026/9/3 22:25:16

Grok代购谈判Bot技术拆解:AI Agent如何实现比价与下单

“如果 Grok 真的能帮你自动比价、谈价、下单,那么以后你说一句‘帮我找一部 5000 元以内、适合拍照和打游戏的手机,价格越低越好’,就不只是一次搜索,而是一笔委托任务。” 最近关于“Grok Bot 可代购并谈判最优价格”的说法在社…

作者头像 李华
网站建设 2026/9/3 22:24:36

基于异步流水线架构的实时人体姿态与动作识别系统开发实践

简介:这是一套基于Python开发的人体姿态与动作识别系统,面向人工智能初学者、计算机视觉方向学生及项目实践者,解决人体关键点检测与常见动作分类的工程落地问题,适用于健身指导、行为分析、人机交互等场景。资源包共45个文件&…

作者头像 李华