说实话,向量数据库这词我听了快半年了,每次看 RAG 相关的文章都能看到 Milvus、Pinecone 这些名字,但一直没往心里去。
我脑子里一直有个疑问:不就是存数据吗?MySQL 不够用?Redis 不够快?为啥非得整个新东西叫 "向量数据库"?
直到上周我想整个 AI 日记本 ------ 就是你写日记,然后可以用自然语言问它 "我最近啥时候最开心",它能从你日记里翻出相关内容再给你总结。做着做着,我就明白向量数据库到底在干嘛了。
从一个笨想法开始
最开始我想的很简单:日记存在 MySQL 里,不就完了?
标题、内容、日期、心情标签,全是结构化的,CRUD 一把梭。然后用户问 "我最近做了什么让我感到快乐的事情",我就...... 我就啥?
我总不能 SELECT * FROM diary WHERE content LIKE '%快乐%' 吧?那日记里写 "心情愉快"、"感觉很有成就感"、"春天真美好" 这些怎么办?这些句子里没有 "快乐" 两个字,但表达的就是开心的意思啊。
这时候我才反应过来 ------ 传统数据库查的是 "是什么",而我现在要查的是 "像什么"。
关键词匹配搞不定语义相似的问题,这就是向量数据库要解决的事。
向量到底是个啥
别被名字吓住,向量就是一串数字。
你把一句话丢给 Embedding 模型,它给你返回一堆浮点数,比如 1024 个。这串数字就是那句话的 "语义指纹"------ 意思相近的话,它们的指纹在空间里离得就近。
打个比方,"今天天气很好,去公园散步了,心情愉快" 和 "周末和朋友去爬山,天气很好,心情也很放松",这两句话字面不一样,但在 1024 维的空间里,它们的向量点挨得特别近。
而 "今天工作很忙,完成了一个重要的项目里程碑" 虽然也提到了 "成就感",但整体语义离 "户外放松" 那条就远一些。
向量数据库干的事,就是把这些高维向量存起来,然后你扔一个查询向量进去,它能快速告诉你 "库里哪几条跟你这条最像"。
我的理解MySQL = 按关键词精确查找(找书名里有 "三体" 的书)Milvus = 按语义相似度查找(找跟 "科幻宇宙文明" 感觉最像的书)
先跑通最小 demo
我用的是 Zilliz,就是 Milvus 的全托管服务,不用自己搭集群,白嫖额度够我折腾了。
先整个最基础的,连接上、插两条数据、搜一下。
javascript
import { MilvusClient, IndexType, MetricType } from "@zilliz/milvus2-sdk-node";
import 'dotenv/config';
const ADDRESS = process.env.MILVUS_ADDRESS;
const TOKEN = process.env.MILVUS_TOKEN;
async function main() {
const client = new MilvusClient({
address: ADDRESS,
token: TOKEN,
});
console.log("正在连接 zilliz server...");
const checkHealth = await client.checkHealth();
if (!checkHealth.isHealthy) {
console.error("连接失败", checkHealth.reasons);
return;
}
console.log("连接成功,集群状态正常");
const COLLECTION_NAME = 'test';
const DIMENSION = 4; // 维度,先玩个4维的,意思意思
try {
// 集合就相当于MySQL的表
await client.createCollection({
collection_name: COLLECTION_NAME,
dimension: DIMENSION,
auto_id: true, // 自动生成ID
});
console.log('创建集合成功');
// 建索引,这个后面细说
await client.createIndex({
collection_name: COLLECTION_NAME,
field_name: 'vector',
index_type: IndexType.AUTOINDEX,
metric_type: MetricType.COSINE
});
console.log('索引创建成功');
// 插两条数据,比写SQL爽多了
const data = [
{ vector: [0.1, 0.2, 0.3, 0.4], content: '这是第一行数据' },
{ vector: [0.5, 0.6, 0.7, 0.8], content: '这是第二行数据' },
];
const insertRes = await client.insert({
collection_name: COLLECTION_NAME,
data
});
console.log('插入数据成功', insertRes.IDs);
// 搜一下
const searchRes = await client.search({
collection_name: COLLECTION_NAME,
data: [[0.1, 0.2, 0.3, 0.4]],
limit: 2,
output_fileds: ['content'], // 注意这行,坑了我半小时
});
console.log(JSON.stringify(searchRes.results, null, 2));
} catch (err) {
console.error('集合可能存在或创建出错', err.message);
}
}
main().catch((err) => { console.error(err); });
跑通那一刻其实挺有成就感的,虽然 4 维向量没啥实际意义,但整个流程通了 ------ 存进去、搜出来、能拿到结果。
索引是怎么回事
说到索引,我第一反应就是 MySQL 的 B + 树。但向量的索引完全不是一回事。
你想啊,1024 维的向量,数据量如果有个几百万条,每次查询都要把查询向量和库里所有向量挨个算一遍相似度,那就是 O (n) 的暴力搜索,数据量大了能慢到你怀疑人生。
这就像图书馆里找《三体》,没有索引的话,你得从第一排书架一本一本翻。但有了分类索引,你直接去 "文学馆→小说→科幻" 那个区域找,范围一下子就缩小了。
Milvus 里的 IVF_FLAT 索引大概就是这么个思路:先把向量空间分成好多 "簇"(聚类),查询的时候先看你的查询向量属于哪个簇,然后只在那个簇里搜。
从几百万条缩小到几千条,速度直接从秒级变毫秒级。
注意索引不是建得越多越好,也不是所有场景都需要。数据量小的时候(比如几千条),暴力搜反而更快更准,建索引纯属浪费。
真正的项目:AI 日记本
demo 跑通了,接下来搞点实际的 ------ 把日记内容向量化存进去,然后用自然语言查询。
先建集合,字段得定义清楚:
javascript
await client.createCollection({
collection_name: COLLECTION_NAME,
fields: [
{ name: "id", data_type: DataType.VarChar, max_length: 50, is_primary_key: true },
{ name: "vector", data_type: DataType.FloatVector, dim: VECTOR_DIM }, // 1024维向量
{ name: 'content', data_type: DataType.VarChar, max_length: 5000 },
{ name: 'date', data_type: DataType.VarChar, max_length: 50 },
{ name: 'mood', data_type: DataType.VarChar, max_length: 50 },
{ name: 'tags', data_type: DataType.Array, element_type: DataType.VarChar, max_capacity: 10, max_length: 50 },
],
auto_id: true,
});
然后把日记内容通过 Embedding 模型转成向量,再插进去:
javascript
const diaryContents = [
{
id: 'diary_001',
content: '今天天气很好,去公园散步了,心情愉快。看到了很多花开了,春天真美好。',
date: '2026-01-10',
mood: 'happy',
tags: ['生活', '散步']
},
{
id: 'diary_002',
content: '今天工作很忙,完成了一个重要的项目里程碑。团队合作很愉快,感觉很有成就感。',
date: '2026-01-11',
mood: 'excited',
tags: ['工作', '成就']
},
// ... 还有几条就不贴了
];
// 批量生成embedding
const diaryData = await Promise.all(
diaryContents.map(async (diary) => ({
...diary,
vector: await getEmbedding(diary.content), // 把文本转成向量
}))
);
const insertResult = await client.insert({
collection_name: COLLECTION_NAME,
data: diaryData,
});
插完之后,最激动的时刻来了 ------ 搜一下试试:
javascript
const query = '我想看看关于户外活动的日记';
const queryVector = await getEmbedding(query); // 查询语句也转成向量
const searchResult = await client.search({
collection_name: COLLECTION_NAME,
vector: queryVector,
limit: 2,
metric_type: MetricType.COSINE, // 余弦相似度
output_fields: ['id', 'content', 'date', 'mood', 'tags']
});
你猜怎么着?我搜 "户外活动",它真的给我返回了 "去公园散步" 和 "去爬山" 那两条!虽然这两条日记里根本没有 "户外活动" 这四个字。
那一刻我真的觉得,向量搜索这玩意儿有点东西。
再往前走一步:RAG
光搜出来还不够,用户想要的是 "回答",不是 "相关日记列表"。
所以我又加了一步:把搜出来的日记塞给大模型,让它基于这些内容来回答问题。这就是 RAG------ 检索增强生成。
javascript
async function answerDairyQuestion(question, k = 2) {
// 第一步:检索相关日记
const retrieveDiaries = await retrieveRelevantDairies(question, k);
if (retrieveDiaries.length === 0) {
console.log('没有找到相关日记');
return;
}
// 第二步:把检索到的内容拼成prompt
const content = retrieveDiaries.map((diary, i) => `
[日记 ${i+1}]
日期:${diary.date}
心情:${diary.mood}
标签:${diary.tags?.join(', ')}
内容:${diary.content}
`).join('\n\n---\n\n');
const prompt = `你是一个温暖贴心的 AI 日记助手,基于用户的日记内容回答问题...
${content}
用户问题:${question}
...`;
// 第三步:丢给大模型
const response = await model.invoke(prompt);
console.log(response.content);
}
整个流程就三步:把问题向量化 → 去向量库里搜相似的 → 把搜到的内容给大模型让它组织语言回答。
说起来简单,但我在做的时候踩了好几个坑。
我踩过的坑
坑一:output_fileds 拼错了
对,就是上面 demo 里写的那个 output_fileds。我写的是 fileds 不是 fields,少了个 e。
搜是能搜到,但返回结果里就是没有我要的字段,全是空的。我盯着代码看了十分钟,还去翻文档,怀疑是不是 Zilliz 的 bug。
后来复制文档里的字段名一对比...... 行吧,手滑了。
坑二:维度对不上
Embedding 模型返回的维度,和你建集合时指定的 dim,必须一模一样。
我用的是通义千问的 text-embedding-v3,指定了 1024 维。但一开始我写的是 1536(脑子里默认是 OpenAI 的维度),插数据的时候直接报错。
这个还好,报错信息挺明显的,改一下维度就好了。
坑三:忘了 load collection
建完索引之后,要调用 loadCollection 把集合加载到内存里,不然搜不了。
我第一次写的时候把这步漏了,search 的时候报了个错,意思大概是 "集合没加载"。我还纳闷呢,我不是建好了吗?
后来才明白,Milvus 是把数据存在磁盘上的,查询之前得先 load 到内存里才能搜。就像你看书得先把书从书架上拿下来翻开,不能隔着书架直接读。
javascript
console.log('loading collection...');
await client.loadCollection({
collection_name: COLLECTION_NAME,
});
console.log('collection loaded');
坑四:client.connectPromise 这个细节
我一开始直接 new MilvusClient(...) 然后马上调用方法,有时候能行有时候不行,挺玄学的。
后来看了别人的代码,发现大家都会等一下 connectPromise:
javascript
const client = new MilvusClient({ address, token });
await client.connectPromise; // 先等连接建立好
console.log('Connected');
对,就跟数据库连接池一样,new 完了不等于连接已经建立好了,得等握手完成。
往源码里瞅了一眼
说实话,Node.js 的 SDK 源码没那么难读,我翻了一下 search 方法的实现。
大概就是把你传的参数组装成 gRPC 的请求,发给 Milvus 服务端,然后把返回的结果处理一下给你。真正的向量搜索逻辑全在服务端,SDK 就是个通信层。
这也合理,毕竟向量数据库的核心是索引算法和查询优化,这些都得用 C++ 之类的写才能跑得快。Node.js SDK 就是封装了一下调用接口。
最后扯两句
做完这个小项目,我对向量数据库的理解算是落地了。以前看文章总觉得 "向量数据库" 是个很高大上的概念,真动手做一遍才发现 ------
它就是给 AI 用的 "语义图书馆"。 传统数据库按关键词找,向量数据库按意思找。就这么简单。
但也不是说有了向量数据库就万能了:
- 数据量小的时候真没必要,几千条数据暴力搜就够了,上 Milvus 纯属增加复杂度
- 向量的质量取决于 Embedding 模型,模型不行,搜出来的结果就离谱
- 相似度搜索不等于精确匹配,它返回的是 "最像的",不一定是 "对的"
- RAG 也不是银弹,搜出来的内容如果不相关,大模型照样会胡说八道
不过话说回来,做这个 AI 日记本的过程真的挺有意思的。从 "向量数据库有啥用" 的疑惑,到亲手把数据存进去、搜出来、再让大模型基于这些内容回答问题,整个链路跑通的时候,那种恍然大悟的感觉,比看十篇文章都管用。
如果你也在学向量数据库或者 RAG,我的建议是别光看,找个小项目动手做一遍。不用搞太复杂,就像我这样整个简单的日记本,把流程跑通,理解一下子就深了。
搞懂了记得回来留个言,我也想看看你的理解是不是跟我一样,或者你有啥更有意思的用法。