什么是向量数据库:给大模型接上「按意思查」的知识库

向量数据库,简单说就是专门用来存「向量」、并能极快地找出「最相似向量」的数据库。要理解它,得先接上 embedding 这个概念——如果对「文本怎么变成向量」还不熟,可以先看 从 Prompt 到输出 和 多模态 Token。


一、从 embedding 说起:为什么需要它

我们已经知道:文本、图像、音频经过模型都会变成向量(embedding),而且有个关键性质——语义相近的东西,向量在空间里也相近。「猫」和「猫咪」的向量挨得近,和「股票」的向量离得远。

那问题来了:我有一百万段文档、每段都存成了向量。用户问一句话(也变成一个向量),我想找出语义最接近的那几段——

  • 传统数据库只会精确匹配 / 关键词匹配:WHERE title = 'xxx'、LIKE '%猫%'。它不懂「意思相近」。
  • 我要的是按「向量距离」找最近的,而不是按字段相等找。

这就是向量数据库要干的事:最近邻搜索(Nearest Neighbor Search)——给一个查询向量,返回库里离它最近的 top-k 个。

1
2
查询向量 q  ──►  在百万向量里找离 q 最近的 5 个  ──►  返回对应的原文
(用余弦相似度 / 点积 / 欧氏距离衡量「近」)

二、为什么要「专门的」数据库:暴力算太慢

最朴素的办法:拿 q 和库里每个向量都算一遍距离,排序取前 k。但向量动辄几百上千维、数量上百万——每次查询都全量比一遍,太慢了。

所以向量数据库的核心本事是 ANN(Approximate Nearest Neighbor,近似最近邻)索引:用一点点精度换巨大的速度,不用比全部就能快速锁定「大概率最近的那些」。常见索引:

  • HNSW:把向量连成一张分层的图,查询时沿着图「跳着找」,又快又准,最主流。
  • IVF:先把向量聚成很多簇,查询时只在最近的几个簇里找。
  • PQ(乘积量化):把向量压缩,省内存、加速比较。

关键词是「近似」:它不保证 100% 找到绝对最近的那个,但能在毫秒级返回「足够近」的结果,这在实际应用里完全够用。


三、它实际存什么

一条记录(很多库叫 point / entry)通常是四样东西打包在一起,外加数据库自己维护的索引结构:

1
2
3
4
5
6
7
8
9
10
11
12
{
"id": "doc42_chunk7",
"vector": [0.0123, -0.0456, 0.0789, "...", 0.0021],
"text": "员工每年享有 15 天带薪年假,入职满 3 年后增加至 20 天……",
"metadata": {
"source": "employee-handbook.pdf",
"doc_id": "doc42",
"chunk_index": 7,
"page": 12,
"tag": "HR"
}
}
  • id:唯一标识,用来更新 / 删除 / 去重。RAG 里常编码成「文档号 + 第几块」(如 doc42_chunk7),方便定位和重建。
  • vector:真正用来检索的 embedding,一个定长的浮点数组,维度由模型决定(768 / 1024 / 1536…)。同一集合里所有向量维度必须一致,距离度量(余弦 / 点积 / 欧氏)建库时就定好。相似度搜索只跑在这个字段上,其他字段都不参与找最近邻。
  • 原文 text:被切出来的那段原始文本。存它是为了命中后直接把原文返回塞进 prompt,不用拿 id 再查一遍。(大数据 / 合规场景也可只存 id 引用,原文放对象存储。)
  • metadata:任意结构化附加字段,干三件事——过滤(做「语义相似 + 条件」的混合查询,如 tag = 'HR' 范围内找最相似)、溯源(标注「来自手册第 12 页」)、重建上下文(doc_id + chunk_index 把相邻块拼回去)。为了让过滤也快,这些字段通常单独建索引。

除了记录,数据库还存一块看不见但关键的派生数据——ANN 索引结构(HNSW 的图、IVF 的簇中心、PQ 的码本),插入时自动构建并持久化,往往占用不小的内存 / 磁盘。

记录按集合(Collection)组织(类似关系库的「表」),一个集合统一约定向量维度和距离度量。进阶场景里,一条记录还可能挂多个向量(稠密 + 稀疏做混合检索,或图文多模态向量)。


四、最主要的用途:RAG(检索增强生成)

这才是向量数据库这几年爆火的原因。大模型有两个硬伤:知识会过时、答不了你的私有数据,而且容易「一本正经地编」。RAG(Retrieval-Augmented Generation) 用向量库给它外挂一个可检索的知识库:

1
2
3
4
5
6
7
8
9
【离线建库】
文档 → 切成小块(chunk) → 每块用 embedding 模型变成向量
→ 连同原文一起存进向量库

【在线问答】
用户提问 → 把问题也 embed 成向量
→ 去向量库检索最相似的 top-k 段原文
→ 把这几段塞进 prompt 当上下文
→ 大模型基于这些「查到的资料」作答(有依据、可追溯)

这样模型是「带着查到的资料回答」,而不是凭记忆硬编。它的好处:

  • 知识可更新:换文档、加文档即可,不用重新训练模型。
  • 答案有出处:能告诉你这段回答来自哪份文档,便于核查。
  • 省上下文:不用把整个知识库塞进 prompt,只检索最相关的几段。

Agent 的「长期记忆」、企业知识库问答,底层很多就是这套。


五、有哪些选择

  • 专用向量库:Pinecone、Weaviate、Milvus、Qdrant、Chroma。
  • 给现有数据库加向量能力:pgvector(给 PostgreSQL 加)、Redis、Elasticsearch 等。

小项目用 pgvector 往往就够了,数据量和并发上来了再考虑专用向量库——不要一上来就堆重型组件。


六、进阶:HNSW 索引内部是怎么运转的

第二节说 HNSW「把向量连成分层的图、跳着找」。这里把它拆开——这也是面试最常被追问的点。

1. 核心思想:小世界图 + 分层

  • 小世界图(NSW):每个向量是图上一个节点,和相近的若干节点连边。检索用贪心路由——从某个点出发,每一步都跳到离目标更近的邻居,直到邻居里没有更近的。因为图像社交网络一样「任意两点只隔几跳」,不用比全部就能逼近目标。
  • 分层(Hierarchical):在 NSW 上叠几层「高速公路」,很像跳表。顶层稀疏、跨度大,负责大步逼近目标区域;越往下越密,负责精细定位;底层(Layer 0)包含全部节点。

2. 搜索是怎么走的(看动画)

下面这个动画自动演示了整个过程:★ 是查询目标,盯着高亮的小球看它怎么从顶层大跨度逼近、逐层下降、最后在底层锁定最近邻。

如果动画加载不出来,可直接打开:HNSW 搜索动画

一句话概括这条路径:顶层用很少的跳数冲到目标区域 → 逐层下降、越来越精细 → 底层(含全部节点)做最后精搜,返回 top-k。

3. 每个节点的「层」是怎么定的:随机抽,不是算

这点很反直觉但很关键:一个节点能上到第几层,和它的向量内容、位置完全无关,是插入时按概率公式随机抽的。

1
level = floor( -ln(random(0,1)) × mL )      // mL = 1 / ln(M)

它产生一个指数衰减的分布:

  • 绝大多数节点抽到 level 0(只待在底层);越高层节点呈指数级骤减。以 M=16 为例(mL≈0.36),约 94% 的节点只在 Layer 0,到 Layer 1 的约 6%,再上去零点几个百分点——自然堆成金字塔。
  • 一个节点出现在「从 0 到它 level」的所有层:抽到 level 2 就同时在 Layer 0、1、2。所以越往下层节点越全,Layer 0 永远包含全部节点。

最贴切的类比是跳表:像不停掷硬币,连续正面才升一级——少数「运气好」的节点被随机提拔成高层的「高速公路」。用指数衰减是为了让每高一层节点约为下层的 1/M,层数期望约 log_M(N),从而把搜索压到对数级。

所以上面动画里 5(到顶层)、13(只到中层)、2(只在底层),对应的就是它们各自被随机抽到的 level——谁在高层是运气,不是因为它「更重要」。

4. 三个调优旋钮

参数 含义 调大的影响
M 每个节点保留多少条边 召回更高更准,但更占内存
ef_construction 建图时的候选集大小 图质量更好,但建库更慢
ef_search 查询时的候选集大小 召回更高但查询更慢——速度 / 精度的主旋钮

HNSW 的代价是吃内存(图 + 向量常驻)和删除麻烦(多用标记删除 + 定期重建),但凭又快又准,成了向量检索的事实标准。


七、进阶:文档怎么切块(Chunking)

第四节说 RAG 的第一步是「文档 → 切成小块(chunk)」。这一步看似简单,却直接决定检索的天花板——切得太碎,答案凑不齐;切得太大,embedding 被稀释、检索不准。值得单独讲透。

1. 最常用:递归字符切分(Recursive Splitting)

「递归」的意思是:先在最大的边界(段落)切,块还太大就降一级用更小的边界(句子、逗号)再切,一级级往下,直到每块都不超过设定大小——在「自然断句」和「控制大小」之间找平衡。它是 LangChain 等框架的默认做法。

两个核心参数:

1
2
chunk_size    : 每块多大(常用 256~1024 token)
chunk_overlap : 相邻块重叠多少(常用 chunk_size 的 10%~20%)

为什么要重叠(overlap):如果一个完整意思正好被切在两块边界上,任一块都不完整,检索时两边都可能匹配不上。让相邻块头尾重叠一部分,保证跨边界信息不丢。

2. 用一个段落走一遍

设 chunk_size = 40 字(演示用,设小),分隔符优先级 段落 → 换行 → 句号 → 逗号 → 单字。原文一段:

1
员工每年享有15天带薪年假。入职满3年后,年假增加至20天。年假需提前3天申请,经主管批准后生效。未休年假可结转至次年,但最多保留5天。

递归过程:

1
2
3
第1级 按段落\n\n 切 → 没有空行,整块 66 字 > 40 → 降级
第2级 按换行\n 切 → 没有换行,还是切不开 > 40 → 降级
第3级 按句号。 切 → 切成 4 个句子,每个都 ≤ 40 ✓

切出 4 个句子后,贪心合并回去,只要不超过 40 字就继续拼:

1
2
块1 = 句① + 句②          = 28 字 ≤ 40 ✓   (再加句③→47 超了,收口)
块2 = 句③ + 句④ = 38 字 ≤ 40 ✓

最终切成 2 块,每块都是完整句子,绝不从句子中间切断:

1
2
块1:员工每年享有15天带薪年假。入职满3年后,年假增加至20天。
块2:年假需提前3天申请,经主管批准后生效。未休年假可结转至次年,但最多保留5天。

对比朴素的「固定长度」直接数到第 40 字一刀切,会把 「20天」 劈成 2 和 0天,语义被破坏、这块的 embedding 就废了。递归切分优先在自然边界断,避免了这个问题。

3. 一句话超过 chunk_size 时怎么办

很多人卡在这:合并后超了会不会硬截断?分三种情况,只有最后一种才真的硬切:

  • A. 两句合并会超 → 根本不合并。合并是贪心试探,加之前先算会不会超;会超就不加、开新块。所以「合并超出」的结果是分块,不是截断,句子一个字都没动。
  • B. 单独一句本身就超长 → 递归继续降级。在这句内部用更细的分隔符(逗号 → 词)再切,再贪心合并,仍然在标点边界断。
  • C. 连最小片段都还超长(如超长无标点串、URL、代码)→ 走到最后一级(逐字符)做字符硬切。这是唯一的硬截断,且配合 overlap 把断点尾部带进下一块缓冲。
1
2
分隔符优先级:段落 → 换行 → 句号 → 逗号 → 单字符("")
↑ 只有走到这一级才硬截断(兜底)

也就是说 chunk_size 不是「绝不超过的硬上限」——一个无法再分的原子片段允许略微超出,除非显式开启强制字符切。能在自然边界分块就绝不硬切,硬切只是兜底。

4. 其他策略与 chunk 大小的权衡

方式 怎么切 适合
固定长度 + 重叠 死板按 N 个 token 切 最简单基线,文本均匀时够用
递归字符切分 段落→句子→词 层级切(最常用默认) 通用文档,首选
结构感知切分 按 Markdown 标题 / HTML / 代码函数切 结构化文档、代码、手册
语义切分 用 embedding 找话题断点处切 追求质量、不在乎成本;较新较贵

chunk 大小的权衡:切太小→检索精准但上下文碎、答案凑不齐;切太大→一个向量代表太多内容、embedding 被稀释、检索变差还费 token。经验值 2561024 token、重叠 1020%,再按文档类型和召回效果实测调整。

chunking 是 RAG 里最需要按数据调参的一环,没有放之四海皆准的最优值。先用「递归切分 + 适中 chunk_size + 适度 overlap」作默认,跑通再调。


一句话总结

向量数据库 = 存 embedding、并用 ANN 索引做「按语义相似度找最近邻」的数据库。它把「精确匹配」换成了「意思相近就行」,是给大模型接私有知识、做 RAG 和长期记忆的标准地基。

顺带一提,这和 MCP 能接上:一个「知识库 MCP Server」背后,往往就是一个向量数据库在干检索的活——模型通过 MCP 发起检索,Server 去向量库查回最相关的资料再喂回来。