# 向量数据库入门:从"关键词匹配"到"语义理解"

如果你之前没接触过向量数据库,它做的事情用一句话就能概括:把一段文字变成一个高维空间里的坐标点,然后通过计算坐标之间的距离来找"意思相近"的内容。

听起来有点抽象。所以这篇文章不打算从概念讲到概念,而是直接拿一个实际的 AI 日记本项目来跑一遍------从存数据到语义搜索再到 RAG,覆盖日常开发中最常用到的几个环节。用的技术栈是 Milvus(Zilliz Cloud 托管版)+ LangChain + 阿里云 DashScope 的 Embedding 和 Chat 模型。

传统数据库在做啥

我们平时用 MySQL、PostgreSQL 做 CRUD,核心的查询方式大概两种:

  • 精确匹配WHERE id = 123,你给我一个 id,我返回一条记录
  • 关键词模糊匹配WHERE content LIKE '%爬山%',你给我一个词,我找出包含这个词的记录

这在大多数业务场景下够用了。但当你遇到这种问题的时候,它就有点力不从心了:

"帮我找一下最近心情比较快乐的日记"

"快乐"这个词没直接出现在日记里。日记写的是"今天天气很好,去公园散步了,心情愉快。"------这里用的是"愉快",不是"快乐"。

一个开发者面对这种需求时,通常会这么做:先维护一个同义词表,把"快乐""愉快""高兴""开心"全列上,然后拼一个巨大的 LIKE OR 查询。粗暴,但能勉强跑。问题在于,同义词表维护成本很高,而且"在公园散步很开心"和"今天升职了特别激动",这两句话完全没有相同的词,但人一看就知道都是在讲开心的事。关键词匹配对此束手无策。

这就是向量数据库要解决的问题:不按字面匹配,而是按语义匹配

Embedding:把文字变成坐标

向量数据库的核心依赖是一个叫 Embedding 的东西。你可以把它理解成一个函数:

javascript 复制代码
// 输入:一段文字
// 输出:一个高维向量(比如 1024 个浮点数组成的数组)
const vector = await getEmbedding('今天天气很好,心情愉快');
// vector ≈ [0.023, -0.451, 0.891, ..., 0.332]  // 1024 个浮点数

这个向量不是随机生成的。Embedding 模型在训练过程中学到了词与词、句与句之间的关系,然后把这些语义关系编码到了高维空间中。在这个空间里,意思相近的文字,它们的坐标也靠近。

打个不太严谨但好理解的比方:如果把所有中文句子画在一张地图上,"我今天很开心"和"心情特别愉快"这两个点会落在同一个街区,而"昨天的部署又挂了"则在这张地图的另一个角落。

这意味着你可以用空间距离 来度量语义相似度------这就是向量数据库的底层逻辑。

Milvus 是什么

Milvus 是一个开源的向量数据库,专为处理海量高维向量数据而设计。

拿它和传统数据库做个类比,可能更容易理解它的定位:

场景 传统数据库(如 MySQL) 向量数据库(如 Milvus)
Web 应用 存用户、订单、商品,按 id 和条件查询 ---
AI Agent --- 存知识、记忆、文档,按语义检索

不是说谁替代谁。实践中通常是两边都用------MySQL 管结构化数据的 CRUD,Milvus 管语义搜索和 AI 相关的数据存储。就像一个应用既有 MySQL 存订单,又有 Redis 做缓存,各自解决各自的问题。

Milvus 的几个基本概念:

  • Collection:类似 MySQL 的表。一个存向量数据的地方。
  • Field:Collection 里的字段,可以定义 id、向量字段、普通标量字段(如日期、标签)。
  • Index:向量索引。加速相似度搜索用的,支持多种索引类型(IVF_FLAT、HNSW 等)。
  • Metric Type:相似度计算方式。常用的有 COSINE(余弦相似度)和 L2(欧氏距离)。

Zilliz Cloud:不想自己运维的选择

在 demo 里我们用的其实是 Zilliz Cloud------基于 Milvus 的全托管云服务。打个比方的话,Milvus 和 Zilliz 的关系有点像 MySQL 和阿里云 RDS:一个是开源数据库本身,一个是帮你把运维、扩容、备份这些都管了的云服务。

对于想快速验证想法的场景,直接用托管服务能省下不少折腾基础设施的时间。

从零搭建一个 AI 日记本

接下来基于项目里的实际代码,走一遍完整流程。这个 demo 做的是一个 AI 日记本------你可以用自然语言查询你的日记,比如"我想看看关于户外爬山的日记",即使"户外""爬山"这些词没有精确出现在日记文本中。

项目依赖

json 复制代码
{
  "dependencies": {
    "@zilliz/milvus2-sdk-node": "^3.0.3",
    "@langchain/openai": "^1.5.5",
    "dotenv": "^17.4.2"
  }
}

三个依赖各司其职:

  • @zilliz/milvus2-sdk-node:Milvus 的 Node.js SDK,负责和向量数据库通信
  • @langchain/openai:LangChain 封装的 OpenAI 兼容客户端,这里用来调 Embedding 和 Chat 模型
  • dotenv:加载 .env 里的环境变量

配置环境变量

env 复制代码
MODEL_NAME=qwen-plus
EMBEDDINGS_MODEL_NAME=text-embedding-v3
OPENAI_API_KEY=your-api-key
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
MILVUS_ADDRESS=https://xxx.serverless.ali-cn-hangzhou.cloud.zilliz.com.cn
MILVUS_TOKEN=your-milvus-token

这里用的是阿里云的 DashScope 作为模型服务(兼容 OpenAI 接口格式),Embedding 模型选了 text-embedding-v3,输出 1024 维向量。

第一步:初始化客户端和 Embedding 模型

javascript 复制代码
import { MilvusClient } from '@zilliz/milvus2-sdk-node';
import { OpenAIEmbeddings } from '@langchain/openai';

// Embedding 模型
const embeddings = new OpenAIEmbeddings({
  apiKey: process.env.OPENAI_API_KEY,
  model: process.env.EMBEDDINGS_MODEL_NAME,
  configuration: {
    baseURL: process.env.OPENAI_BASE_URL
  },
});

const getEmbedding = async (text) => {
  return await embeddings.embedQuery(text);
};

// Milvus 客户端 --- Zilliz Cloud 需要开启 secure
const client = new MilvusClient({
  address: process.env.MILVUS_ADDRESS,
  token: process.env.MILVUS_TOKEN,
  secure: true,
  timeout: 120000,
});

// 验证连接
const checkHealth = await client.checkHealth();
if (!checkHealth.isHealthy) {
  console.error('连接失败', checkHealth.reasons);
  return;
}

secure: true 是连 Zilliz Cloud 时容易漏的一个配置------忘了加的话连接会失败。超时时间也建议设长一些,网络波动时 120 秒比默认的短超时更稳。

第二步:写入向量数据

写入的流程是:准备数据 → 每条数据调 Embedding 接口生成向量 → 一起插入 Milvus。

javascript 复制代码
const diaryContents = [
  {
    id: 'diary_001',
    content: '今天天气很好,去公园散步了,心情愉快。看到了很多花开了,春天真美好。',
    date: '2026-01-10',
    mood: 'happy',
    tags: ['生活', '散步']
  },
  // ... 更多日记
];

// 并发生成向量
const diaryData = await Promise.all(
  diaryContents.map(async (diary) => ({
    ...diary,
    vector: await getEmbedding(diary.content)
  }))
);

// 批量插入
const insertResult = await client.insert({
  collection_name: 'ai_dairy',
  data: diaryData
});
console.log(`${insertResult.insert_cnt} 条记录成功插入`);

注意点:每条记录的 content 字段被 Embedding 模型转成了 1024 维的向量,存到了 vector 字段里。之后所有语义搜索都是基于这个向量字段进行的,而不是基于原始的文本内容。

另外,如果网络不太稳定,可以考虑把 Promise.all 改成顺序循环------并发太高时 Embedding 接口容易限流。Demo 代码里两种方式都留了注释,按实际情况选。

第三步:语义搜索

有了向量数据之后,查询的逻辑是:把用户的自然语言查询也转成向量 → 在 Milvus 里找距离最近的几个向量 → 返回对应的原始数据。

javascript 复制代码
const query = '我想看看户外爬山的日记';
const queryVector = await getEmbedding(query);

const searchResult = await client.search({
  collection_name: 'ai_dairy',
  vector: queryVector,
  limit: 2,
  metric_type: MetricType.COSINE,
  output_fields: ['id', 'content', 'date', 'mood', 'tags'],
});

searchResult.results.forEach((item, index) => {
  console.log(`${index + 1}. [Score: ${item.score.toFixed(4)}]`);
  console.log(`   内容: ${item.content}`);
});

这里 metric_type: COSINE 指定用余弦相似度来比较向量。返回结果里的 score 就是相似度得分------越接近 1 越相似。

这个查询虽然包含"户外"和"爬山"这两个词,但搜索并不是在做关键词匹配。它找到的是向量空间里和"户外爬山"这个语义最接近的日记------哪怕那篇日记写的是"周末和朋友去爬山,享受大自然",里面没有一个词和查询语句是精确匹配的。

第四步:RAG --- 把搜索结果喂给大模型

光搜出来还不够。一个完整的 AI 应用通常会接着做 RAG(Retrieval-Augmented Generation):

用户提问 → 向量检索找到相关文档 → 把文档拼进 Prompt → 大模型基于这些文档生成回答

javascript 复制代码
async function answerDiaryQuestion(question, k = 2) {
  // 1. 检索
  const retrievedDiaries = await retrieveRelatedDiaries(question, k);

  // 2. 把检索结果组织成上下文
  const context = retrievedDiaries.map((diary, i) => `
    [日记 ${i + 1}]
    日期: ${diary.date}
    心情: ${diary.mood}
    内容: ${diary.content}
  `).join('\n\n----\n\n');

  // 3. 构造 Prompt
  const prompt = `你是一个温暖贴心的AI日记助手。
  请根据以下日记内容回答问题:
  ${context}
  用户问题: ${question}
  回答要求:
  1. 如果日记中有相关信息,请结合日记内容给出详细温暖的回答。
  2. 可以总结多篇日记的内容,找出共同点或趋势。
  3. 用第一人称"你"来称呼日记的作者。
  AI助手的回答:`;

  // 4. 大模型生成回答
  const response = await model.invoke(prompt);
  return response.content;
}

这个流程的价值在于:大模型本身没有你的日记数据,但通过 RAG,你可以把相关的日记片段作为"参考资料"塞进 Prompt,让模型基于这些资料回答问题。模型不会编造你日记里没有的事(至少被 prompt 约束着不会),回答也更具体、更个性化。

这套技术栈在实际项目中的位置

如果你在做一个 AI Agent 产品,向量数据库通常会承担这些角色:

  • 长期记忆:Agent 把对话摘要、用户偏好等信息向量化存起来,跨会话保持记忆
  • 知识库检索:把文档、FAQ、规章制度等切分后向量化,用户提问时检索相关内容
  • 语义推荐:根据用户历史行为的语义特征推荐相似内容

本质上都是同一条链路:内容 → 向量 → 存储 → 查询向量 → 相似度搜索 → 返回结果

踩坑备忘

在写这个 demo 的过程中遇到几个容易卡住的地方,记录一下:

  1. Zilliz Cloud 必须配 secure: true。忘了加的话连接一直超时,报错信息也不会直接告诉你原因。

  2. 别反复 loadCollection 。对于 Zilliz 托管集群,Collection 创建好之后已经在内存里了(Serverless 架构会自动管理)。反复调用 loadCollection 反而容易触发超时。Demo 里的 index.mjs 就因为这个踩过坑,相关代码后来注释掉了。

  3. Embedding 接口注意限流 。网络好的时候 Promise.all 并发生成向量很快,但某些 Embedding 服务对并发数有限制,遇到报错就改顺序执行。

  4. dimensions 参数不是所有模型都支持text-embedding-v3 支持指定输出维度,但有些模型不支持,传了会报错。Demo 里 index.mjs 也留了注释提示。

  5. .env 里的变量名要统一 。代码里有的地方写 process.env.OPENAI_API_KEY,有的写 process.env.EMBEDDINGS_API_KEY。.env 里统一用一个名字,否则某段代码静默失败。

小结

向量数据库解决的核心问题其实就一个:让你能按"意思"而不是"字面"来查数据。它的实现也不复杂------Embedding 模型把内容转成向量,向量数据库负责存和检索,应用层在检索结果的基础上做后续处理。

如果你的项目里有"语义搜索""相似内容推荐""AI 记忆"这类需求,向量数据库是一个值得了解的工具。Milvus 是目前这个领域比较成熟的开源选择,Zilliz Cloud 则适合不想自己折腾运维的场景。

本项目的完整代码放在 demo/ 目录下,四个文件分别对应上面讲的四个步骤,可以对照着跑一遍。

相关推荐
ACP广源盛139246256731 小时前
DeepSeek‑V4‑Flash 公测@ACP#昇腾 950 国产算力组合落地,国产 PCIe 交换芯片 IX9104 有哪些硬件机会
大数据·数据库·人工智能·分布式·单片机·嵌入式硬件·microsoft
进击的女IT1 小时前
通过mysql中的Data目录恢复数据库数据
数据库·mysql
yuezhilangniao2 小时前
AI工具全家桶:从开发到运维,从数据库到产品经理-含魔塔社区简介
运维·数据库·人工智能
gb42152872 小时前
ChromaDB向量数据库的特点
数据库
IvorySQL2 小时前
PostgreSQL 日报| GiST 索引扫描可见性缺陷(8 月 3 日)
数据库·人工智能·postgresql·开源
xiaoxiangsiyan2 小时前
LNMP + Redis Sentinel 高可用架构部署手册(续)
运维·网络·数据库·redis·缓存·架构·sentinel
刘婉晴2 小时前
【Web漏洞】SQL 注入实战技巧
前端·数据库·sql
CodeHackerBhx3 小时前
Spring Boot 4 防重复提交:从接口幂等到 Redis 分布式锁的完整实践
java·数据库·spring boot·redis·分布式
xlxxy_3 小时前
外部系统调用SAP接口遇到的一些报错
开发语言·数据库·sap·abap