向量数据库原理:Embedding、相似度检索、索引结构一次讲透
很多同学搭 RAG 系统时,第一反应是"装个向量数据库、调个 API 就完了"。结果上线后发现:检索结果不相关、速度越来越慢、换个 Embedding 模型全部数据要重导……这些坑的根源,都是没搞懂向量数据库底层到底在干什么。
今天这篇,我们从 Embedding 原理到相似度计算,再到索引结构和数据库选型,一次性讲透。读完这篇,你再去用 Milvus、Pinecone 这些工具,就是知其所以然了。
01 Embedding 是什么:文本到向量的桥梁
Embedding 的本质是把人类语言映射到高维数学空间,让"语义相近"的文本在空间中"距离相近"。
你可以这样理解:每个文本经过 Embedding 模型处理后,会变成一个固定长度的浮点数数组(向量)。比如一段话经过 OpenAI 的 text-embedding-3-small 模型处理后,会变成一个 1536 维的向量。
"LangChain 是一个 AI 应用开发框架"
↓ Embedding 模型
[0.0123, -0.0456, 0.0789, ..., 0.0321] // 1536 个浮点数
为什么这玩意能做语义检索?因为 Embedding 模型在训练时,见过海量的文本对——它学会了"意思相近的句子应该映射到相近的位置"。所以"如何做饭"和"烹饪教程"的向量会很接近,而"如何做饭"和"量子力学"的向量就会差很远。
在 LangChain.js 中,生成 Embedding 只需要几行代码:
importOpenAIEmbeddingsfrom"@langchain/openai"constnewOpenAIEmbeddingsmodel"text-embedding-3-small"dimensions1536// 可选,控制输出维度// 单条文本 EmbeddingconstawaitembedQuery"LangChain 是一个 AI 应用开发框架"consoleloglength// 1536consolelogslice05// [0.0123, -0.0456, 0.0789, ...]// 批量 Embedding(存储时用)constawaitembedDocuments"LangChain 是一个 AI 应用开发框架""向量数据库用于存储和检索向量""RAG 是检索增强生成的缩写"consoleloglength// 3
这里有个关键区别:embedQuery 用于查询时的单条文本,embedDocuments 用于批量存储文本。某些模型对查询和文档会做不同处理(比如加前缀),所以不要混用。
✅ 正确:查询用 embedQuery,存储用 embedDocuments
❌ 错误:为了省事全用 embedDocuments,某些模型下检索效果会明显变差
02 Embedding 模型选型:钱、效果、速度的三角博弈
没有"最好"的 Embedding 模型,只有最适合你场景的。
目前主流的 Embedding 模型分两大阵营:
商业模型
| 模型 | 维度 | 最大 Token | 特点 |
|---|---|---|---|
| text-embedding-3-small | 1536(可调) | 8191 | 性价比最高,适合大多数场景 |
| text-embedding-3-large | 3072(可调) | 8191 | 精度更高,成本翻倍 |
| text-embedding-ada-002 | 1536 | 8191 | 上一代,不推荐新项目用 |
开源模型
| 模型 | 维度 | 特点 |
|---|---|---|
| BGE-large-zh-v1.5 | 1024 | 中文场景最强开源之一 |
| BGE-m3 | 1024 | 多语言、多粒度、多功能 |
| E5-large-v2 | 1024 | 英文场景表现优秀 |
| GTE-large | 1024 | 通义千问团队出品 |
选型建议:
场景判断流程:
你的数据主要是中文?
├── 是 → 预算充足?
│ ├── 是 → text-embedding-3-small(简单省心)
│ └── 否 → BGE-large-zh-v1.5 / BGE-m3(本地部署)
└── 否 → 多语言混合?
├── 是 → BGE-m3 / text-embedding-3-small
└── 否(纯英文)→ E5-large-v2 / text-embedding-3-small
在 LangChain.js 中使用开源模型(通过 HuggingFace 或本地部署的 Ollama):
importOllamaEmbeddingsfrom"@langchain/ollama"// 使用本地 Ollama 部署的 BGE 模型constnewOllamaEmbeddingsmodel"bge-large-zh-v1.5"baseUrl"http://localhost:11434"constawaitembedQuery"向量数据库原理"consoleloglength// 1024
✅ 正确:根据数据语言和预算选模型,先在小数据集上对比效果再做决定
❌ 错误:无脑选最贵的 large 模型——维度越高,存储成本和检索延迟都会增加
03 相似度计算:三种距离度量的本质区别
把文本变成向量之后,“找最相关的内容"就变成了"找距离最近的向量”。 但"距离"有好几种算法,用错了效果天差地别。
余弦相似度(Cosine Similarity)
衡量两个向量方向的夹角,不关心长度。值域 [-1, 1],1 表示完全同方向,0 表示正交,-1 表示完全反向。
余弦相似度 = (A · B) / (|A| × |B|)
向量 A = [1, 2, 3]
向量 B = [2, 4, 6]
余弦相似度 = 1.0 (方向完全相同,虽然长度不同)
最常用。 绝大多数 Embedding 模型输出的向量已经做了归一化(长度为 1),这时候余弦相似度等价于内积。
欧氏距离(Euclidean Distance / L2)
衡量两个向量在空间中的直线距离。值越小越相似。
欧氏距离 = √(Σ(Ai - Bi)²)
向量 A = [1, 0]
向量 B = [0, 1]
欧氏距离 = √2 ≈ 1.414
对向量长度敏感。如果你的 Embedding 没有归一化,同样语义的文本可能因为长度不同而被判为"不相似"。
内积(Inner Product / Dot Product)
内积 = Σ(Ai × Bi)
向量 A = [1, 2, 3]
向量 B = [4, 5, 6]
内积 = 1×4 + 2×5 + 3×6 = 32
当向量已归一化时,内积 = 余弦相似度。速度最快,因为不需要额外计算模长。
怎么选?
你的 Embedding 模型输出已归一化?
├── 是(OpenAI、BGE 等大多数模型)
│ → 用内积(IP),速度最快,效果等于余弦相似度
├── 不确定
│ → 用余弦相似度(COSINE),最稳妥
└── 否(原始向量未归一化)
→ 用余弦相似度(COSINE)或先手动归一化再用内积
用 TypeScript 手动实现看一下直觉:
// 余弦相似度functioncosineSimilaritya: number[], b: number[]numberlet000forlet0lengthreturnMathsqrtMathsqrt// 欧氏距离functioneuclideanDistancea: number[], b: number[]numberlet0forlet0length2returnMathsqrt// 内积functioninnerProducta: number[], b: number[]numberlet0forlet0lengthreturn// 验证:归一化向量下 cosine ≈ inner productconst0.60.8// 长度 = 1const0.80.6// 长度 = 1consolelogcosineSimilarity// 0.96consoleloginnerProduct// 0.96 完全一致!
✅ 正确:搞清楚模型输出是否归一化,再选距离度量
❌ 错误:用欧氏距离搜索未归一化的向量,结果一团糟
04 向量索引结构:从暴力搜索到毫秒级检索
向量多了以后,逐一对比(暴力搜索)不现实——100 万条 1536 维向量暴力搜一次要几秒。索引结构就是加速检索的核心。
暴力搜索(Flat / Brute Force)
查询向量 Q
↓ 依次计算与每个向量的距离
[V1] → distance = 0.85
[V2] → distance = 0.32
[V3] → distance = 0.91 ← 最相似
...
[Vn] → distance = 0.67
- 精度:100%(一定能找到最近的)
- 速度:O(n),数据量一大就完蛋
- 适用:数据量 < 10 万,或需要精确结果的场景
IVF(Inverted File Index)
把向量空间划分成多个区域(簇),查询时只搜索最近的几个簇,大幅减少计算量。
训练阶段:K-Means 聚类,把 N 个向量分到 K 个簇中
簇 1 簇 2 簇 3
[V1,V5,V8] [V2,V3,V9] [V4,V6,V7]
C1 C2 C3
(质心) (质心) (质心)
查询阶段:
1. 计算 Q 与所有质心的距离
2. 选 nprobe 个最近的簇(比如 nprobe=2)
3. 只在这些簇内暴力搜索
Q → 最近质心: C2, C3
→ 只搜索 [V2,V3,V9,V4,V6,V7] // 搜索量减少了 1/3
- 精度:近似(可能错过在其他簇中的好结果)
- 速度:O(K + nprobe × n/K)
- 适用:百万级数据,对召回率要求不是极致的场景
HNSW(Hierarchical Navigable Small World)
目前最受欢迎的索引算法。 构建多层级的图结构,上层是"高速公路"快速定位大致区域,下层是"小路"精确查找。
Layer 2(最稀疏): V1 ──────── V7
\ /
Layer 1: V1 ── V3 ── V7 ── V9
| × | | × |
Layer 0(最稠密): V1-V2-V3-V4-V5-V6-V7-V8-V9
查询流程:
1. 从最高层的入口节点出发
2. 在当前层贪心搜索最近邻
3. 找不到更近的了,下沉一层
4. 重复直到最底层,返回结果
- 精度:非常高(通常 95%+ 召回率)
- 速度:O(log n),毫秒级
- 内存:需要额外存储图结构,内存占用较大
- 适用:对检索质量要求高、数据量百万到千万级
PQ(Product Quantization,乘积量化)
把高维向量压缩成低精度编码,用空间换时间。
原始向量(1536维)
[0.12, 0.34, ..., 0.56, 0.78]
↓ 切分成 M 个子空间(比如 M=8)
[子空间1: 192维] [子空间2: 192维] ... [子空间8: 192维]
↓ 每个子空间用 K-Means 聚类,用质心编号代替
[ 编号: 42 ] [ 编号: 15 ] ... [ 编号: 78 ]
↓ 压缩后
[42, 15, ..., 78] // 8 个 uint8,只需 8 字节!
原来 1536 维 float32 需要 6144 字节,压缩后只需 8 字节,压缩比高达 768 倍。
- 精度:有损压缩,精度最低
- 速度:超快
- 内存:极省
- 适用:亿级数据,对精度要求不高但需要控制成本
各索引对比总览
┌──────────┬─────────┬─────────┬──────────┬──────────────────┐
│ 索引 │ 精度 │ 速度 │ 内存 │ 适用数据量 │
├──────────┼─────────┼─────────┼──────────┼──────────────────┤
│ Flat │ ★★★★★ │ ★☆☆☆☆ │ ★★★☆☆ │ < 10万 │
│ IVF │ ★★★★☆ │ ★★★☆☆ │ ★★★☆☆ │ 10万 - 500万 │
│ HNSW │ ★★★★★ │ ★★★★★ │ ★★☆☆☆ │ 10万 - 5000万 │
│ PQ │ ★★★☆☆ │ ★★★★☆ │ ★★★★★ │ > 1000万 │
│ IVF+PQ │ ★★★★☆ │ ★★★★☆ │ ★★★★☆ │ > 1000万 │
└──────────┴─────────┴─────────┴──────────┴──────────────────┘
✅ 正确:数据量 < 10 万用 Flat,10 万-千万用 HNSW,亿级用 IVF+PQ
❌ 错误:不管数据量多少都用 HNSW——10 亿条数据 HNSW 内存直接爆炸
05 向量数据库选型:一张表搞清楚
向量数据库 = 向量索引 + 元数据存储 + 过滤 + CRUD + 分布式(可选)。 光有 FAISS 这样的索引库还不够,生产环境你需要一个完整的数据库。
┌─────────────────────────────────────────────────────────┐
│ 向量数据库 │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ 向量索引引擎 │ │ 元数据存储 │ │ 过滤/查询引擎 │ │
│ │ HNSW/IVF/PQ │ │ key-value │ │ WHERE color= │ │
│ │ │ │ 文档原文/标签 │ │ "red" AND ... │ │
│ └──────┬──────┘ └──────┬───────┘ └──────┬────────┘ │
│ └────────────────┼─────────────────┘ │
│ ┌──────┴───────┐ │
│ │ API 层 │ │
│ │ CRUD/搜索 │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
主流选型对比
| 特性 | Milvus | Pinecone | Chroma | FAISS | Weaviate |
|---|---|---|---|---|---|
| 类型 | 开源/自部署 | 全托管 SaaS | 开源/轻量 | 索引库 | 开源/自部署 |
| 分布式 | ✅ | ✅ | ❌ | ❌ | ✅ |
| 元数据过滤 | ✅ 强 | ✅ 强 | ✅ 基础 | ❌ | ✅ 强 |
| 最大数据量 | 百亿级 | 十亿级 | 百万级 | 十亿级 | 十亿级 |
| 上手难度 | 中等 | 极低 | 极低 | 低 | 中等 |
| 适合场景 | 大规模生产 | 快速上线 | 原型/小项目 | 离线实验 | 生产+语义搜索 |
| LangChain.js 支持 | ✅ | ✅ | ✅ | ✅ | ✅ |
选型决策流程:
你在做什么?
├── 快速原型 / 个人项目 / 数据量 < 100 万
│ → Chroma(内存模式秒启动)
├── 生产环境,不想运维
│ → Pinecone(全托管,按量付费)
├── 生产环境,数据量大,需要自控
│ → Milvus(功能最全,社区最活跃)
├── 纯做实验,不需要持久化
│ → FAISS(纯索引库,最灵活)
└── 需要混合搜索(向量 + 关键词)
→ Weaviate / Milvus
✅ 正确:先用 Chroma 跑通流程,验证可行再迁移到 Milvus/Pinecone
❌ 错误:一上来就搭 Milvus 集群,结果发现你只有 1000 条数据
06 LangChain.js 向量存储实战
LangChain.js 提供了统一的 VectorStore 接口,切换不同数据库只需要换一行初始化代码。
用 Chroma 快速上手
importOpenAIEmbeddingsfrom"@langchain/openai"importChromafrom"@langchain/community/vectorstores/chroma"importDocumentfrom"@langchain/core/documents"constnewOpenAIEmbeddingsmodel"text-embedding-3-small"// 准备文档constnewDocumentpageContent"LangChain 是一个用于构建 AI 应用的开发框架"metadatasource"docs"category"framework"newDocumentpageContent"向量数据库可以存储和检索高维向量数据"metadatasource"docs"category"database"newDocumentpageContent"RAG 通过检索外部知识来增强大模型的回答质量"metadatasource"blog"category"rag"newDocumentpageContent"Embedding 模型将文本转换为稠密向量表示"metadatasource"docs"category"embedding"// 创建向量存储并插入文档(自动调用 embedDocuments)constawaitChromafromDocumentscollectionName"my-collection"url"http://localhost:8000"// Chroma 服务地址// 相似度搜索constawaitsimilaritySearch"什么是向量数据库?"2// 返回 top 2consolelog// [// Document { pageContent: "向量数据库可以存储和检索高维向量数据", metadata: {...} },// Document { pageContent: "Embedding 模型将文本转换为稠密向量表示", metadata: {...} }// ]
带元数据过滤的搜索
// 只在 source="docs" 的文档中搜索constawaitsimilaritySearch"什么是向量数据库?"2source"docs"// 元数据过滤// 带相似度分数的搜索constawaitsimilaritySearchWithScore"什么是向量数据库?"3forconstofconsolelog`[${score.toFixed(4)}] ${doc.pageContent}`// [0.9234] 向量数据库可以存储和检索高维向量数据// [0.8567] Embedding 模型将文本转换为稠密向量表示// [0.7891] RAG 通过检索外部知识来增强大模型的回答质量
✅ 正确:用 similaritySearchWithScore 获取分数,设置阈值过滤低质量结果
❌ 错误:只用 similaritySearch 取 top-K,不关心分数——可能把完全不相关的也返回了
用 FAISS 做本地实验
importOpenAIEmbeddingsfrom"@langchain/openai"importFaissStorefrom"@langchain/community/vectorstores/faiss"importDocumentfrom"@langchain/core/documents"constnewOpenAIEmbeddingsmodel"text-embedding-3-small"constnewDocumentpageContent"TypeScript 是 JavaScript 的超集"newDocumentpageContent"Python 是一门动态类型语言"newDocumentpageContent"Rust 以内存安全著称"// 创建 FAISS 向量存储constawaitFaissStorefromDocuments// 搜索constawaitsimilaritySearch"静态类型语言"2consolelog0pageContent// "TypeScript 是 JavaScript 的超集"// 保存到磁盘(下次可以直接加载,不用重新 Embedding)awaitsave"./faiss-index"// 从磁盘加载constawaitFaissStoreload"./faiss-index"
用 Milvus 对接生产环境
importOpenAIEmbeddingsfrom"@langchain/openai"importMilvusfrom"@langchain/community/vectorstores/milvus"importDocumentfrom"@langchain/core/documents"constnewOpenAIEmbeddingsmodel"text-embedding-3-small"constnewDocumentpageContent"HNSW 是目前最流行的向量索引算法"metadatatopic"index"level"advanced"newDocumentpageContent"IVF 通过聚类减少搜索范围"metadatatopic"index"level"intermediate"// 连接 Milvus 并创建 CollectionconstawaitMilvusfromDocumentscollectionName"langchain_demo"url"http://localhost:19530"// Milvus 支持指定索引类型indexCreateParamsindex_type"HNSW"metric_type"IP"// 内积(向量已归一化)paramsJSONstringifyM16efConstruction256searchParamsef128// 搜索时的精度参数constawaitsimilaritySearch"向量索引算法"2consolelog
✅ 正确:Milvus 中指定 index_type 和 metric_type,根据数据量调整参数
❌ 错误:全用默认参数——10 万条和 1000 万条数据的最优配置差别很大
07 完整 RAG 检索流程:把所有东西串起来
到这里,我们已经理解了所有核心组件。来看一个完整的 RAG 检索流程,从文档到回答:
┌─────────────────── 离线索引阶段 ───────────────────────┐
│ │
│ 原始文档 │
│ ↓ TextSplitter(上一篇的内容) │
│ 文本块 [chunk1, chunk2, ..., chunkN] │
│ ↓ Embedding Model(embedDocuments) │
│ 向量 [[0.1, 0.2, ...], [0.3, 0.4, ...], ...] │
│ ↓ 写入向量数据库 │
│ VectorStore(带索引 + 元数据) │
│ │
└────────────────────────────────────────────────────────┘
┌─────────────────── 在线查询阶段 ───────────────────────┐
│ │
│ 用户问题: "HNSW 算法的优缺点?" │
│ ↓ Embedding Model(embedQuery) │
│ 查询向量 [0.5, 0.6, ...] │
│ ↓ 向量数据库检索(ANN 近似最近邻) │
│ Top-K 相关文档块 │
│ ↓ 拼接到 Prompt 中 │
│ LLM 生成回答 │
│ ↓ │
│ "HNSW 的优点是检索速度快(O(logN)), │
│ 精度高(95%+召回率);缺点是内存占用大..." │
│ │
└────────────────────────────────────────────────────────┘
这个流程中每一步都可能出问题:Splitter 切得不好、Embedding 模型选错、索引类型不对、top-K 设太小……所以理解每个环节的原理非常重要。
08 常见坑
坑 1:维度不匹配
换了 Embedding 模型但没有重新生成向量,老数据 1536 维、新查询 1024 维,直接报错。
// ❌ 存储时用 text-embedding-3-small(1536维)// 查询时换成了 BGE(1024维)// 结果:维度不匹配,检索直接报错// ✅ 换模型 = 全量重新 Embedding + 重建索引,没有捷径
坑 2:归一化问题
用欧氏距离但向量没归一化,长文本和短文本的向量模长差异大,检索偏向长文本。
// 手动归一化functionnormalizevec: number[]numberconstMathsqrtreduce(sum, v) =>0returnmapv =>// ✅ 用余弦相似度就不用管归一化// ✅ 或者存入前统一归一化,然后用内积
坑 3:索引没重建
持续往向量数据库插数据,但 IVF 的聚类中心还是最初训练时的,新数据分布和旧的差异很大,检索质量下降。
解决:定期重建索引。Milvus 提供 compact 和 createIndex 接口。
坑 4:Top-K 设得不合理
Top-K 太小(比如 1),可能漏掉关键信息;太大(比如 20),塞太多无关内容给 LLM,反而干扰回答质量还浪费 Token。
建议:先取 Top-10,用分数阈值过滤(比如余弦相似度 < 0.7 的扔掉),再取前 3-5 条给 LLM。
坑 5:没做元数据过滤就全库搜索
你的知识库混了产品文档、内部周报、技术博客,用户问产品问题却搜出了周报。加元数据过滤,先缩小范围再做向量搜索。
// ✅ 先过滤再搜索,又快又准constawaitsimilaritySearch"产品定价策略"5category"product-docs"// 只搜产品文档
总结
- Embedding 是 RAG 的地基:把文本变成向量,让机器能用数学方式衡量"语义相似度"
- 模型选型要看场景:OpenAI embedding 简单省心,开源 BGE/E5 适合本地部署和定制化需求,别无脑选最贵的
- 距离度量决定检索质量:向量已归一化就用内积(最快),不确定就用余弦相似度(最稳)
- 索引结构决定检索速度:小数据量用 Flat,百万级用 HNSW(精度和速度最平衡),亿级用 IVF+PQ
- 向量数据库 ≠ 向量索引:生产环境需要元数据过滤、持久化、CRUD、分布式能力,不能只靠 FAISS
- LangChain.js 的 VectorStore 接口统一了上层调用:换数据库只需改初始化代码,业务逻辑不用动
下一篇我们进入 Milvus 实战——从安装部署到百万级数据的索引构建与检索优化,手把手带你跑通生产级向量数据库。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)