←返回文章列表

向量数据库入门:SQL 数据库做不了的"按意思找内容"

RAG大语言模型AI 应用
10 分钟-阅读

SQL 能查"等于、包含、范围",但查不了"意思接近"。文本 → embedding → ANN 索引 → 查询,向量数据库就是为这条路径建造的。

做过几年 web 开发的人都写过各种各样的搜索功能。WHERE name = 'iPhone'、WHERE description LIKE '%手机%'、PostgreSQL 的全文索引 to_tsvector @@ to_tsquery,套路都很熟。

但接到一个新需求会让人愣住:在产品手册里搜"关于退货政策的内容"。文档里写的是"七天无理由换货"。LIKE '%退货%' 找不到——文档里没有这两个字。全文索引也找不到——"退货"和"换货"在索引器看来是不同的词。

这个场景里的"找",需要的不是字符层面的匹配,而是意思层面的匹配。SQL 这套体系不是为此设计的。向量数据库是。

后面是这件事的完整路径——一段文本怎么变成向量、向量之间的距离怎么算、为什么要专门搞个数据库、以及一次完整查询长什么样。

为什么 SQL 数据库做不了"按意思找"

关系型数据库的查询语义无外乎精确匹配加范围筛选:

SELECT * FROM products WHERE price BETWEEN 100 AND 500;
SELECT * FROM orders   WHERE status = 'paid';
SELECT * FROM users    WHERE email = 'alice@example.com';

引擎做的事是看每一行的列值是否满足条件——=、<、>、IN、BETWEEN。

为字符串增加灵活性的方案有两种:

  • LIKE '%关键词%' —— 字符子串匹配
  • 全文索引 to_tsvector / MATCH AGAINST —— 词项级匹配,能处理词形变化和停用词

但这两种都还在"字面层级"工作。"退货"和"换货"是不同字符;"开通 SSO"和"启用单点登录"是不同词项。SQL 没有任何机制理解它们意思接近。

要做"按意思搜",先得解决一件事:如何把"意思"变成可以被比较的东西。

把"意思"变成可以比较的东西

Mikolov 等人 2013 年在 Word2Vec 论文里展示了一件让人印象深刻的事:把每个词训练成一个 300 维的浮点数组(向量),可以做出这样的等式:

vec("国王") - vec("男人") + vec("女人") ≈ vec("女王")

意思不再是抽象概念,而是高维空间里的一个坐标。意思相近的词,坐标也接近。

到了 2018 年的 BERT 和 2019 年的 Sentence-BERT,这件事扩展到整句话甚至整段文本——一段文本可以被映射成一个固定长度的向量,意思接近的两段文本在向量空间里也接近。这个把文本变成向量的过程叫 embedding。

今天用 OpenAI API 做这件事就一行代码:

from openai import OpenAI
client = OpenAI()

resp = client.embeddings.create(
    model="text-embedding-3-small",
    input="七天无理由换货"
)
vec = resp.data[0].embedding   # 1536 个浮点数

vec 是一个长度 1536 的 float 数组。同样的代码对"退货政策"调一次,会得到另一个 1536 维的向量。两个向量在数值上不一样,但因为意思接近,它们在向量空间里"靠得很近"。

一个诚实的注脚:上面那个国王/女王等式是 Word2Vec 时代的演示,画图直觉很好。但 BERT 之后的现代 embedding 不一定能严格满足这种线性等式——它们更多是"意思接近时向量距离也接近"这种相邻关系。直觉留着,等式别太较真。

OpenAI text-embedding-3-small 在 2024 年 1 月发布,价格 0.02 美元每百万 token,便宜到几乎可以忽略;维度默认 1536,效果比 2022 年的 ada-002 全面提升。如果不想依赖闭源 API,BAAI 在 2023 年 8 月开源的 BGE 系列在 MTEB 评测榜上长期占据靠前位置,中英文都能用。

到这里你有了一组"意思的坐标"。下一步是怎么测量"接近"。

向量之间的距离怎么算

三种最常见的度量:

度量 直觉 适用
余弦相似度 看两个向量"指的方向"接不接近,忽略长度 文本 embedding 默认选择
欧氏距离(L2) 看两个点在几何空间里的距离 图像、声音特征也常用
内积 / 点积 同方向相乘求和 计算最快

对文本来说一般选余弦相似度。原因是文本 embedding 关心的是"语义方向"——一段文字写得长还是短,不应该影响它的意思。

有个工程小细节值得记住:如果 embedding 模型输出的是归一化向量(长度 = 1),余弦相似度、L2 距离、内积三者排序结果完全一致。OpenAI 的 embedding 接口默认就归一化,所以选哪个度量其实差不多,挑一个数据库支持得最稳的就行。

数据库该存什么、查什么

最朴素的实现是这样:

  1. 把每个文档调 embedding API,得到一个向量
  2. 把"文档 ID + 文本 + 向量"存进数据库
  3. 用户提问时,把问题也调 embedding,得到查询向量
  4. 让查询向量和库里每个文档向量算余弦相似度
  5. 取相似度最高的 top-K 文档返回

用 PostgreSQL 加 pgvector 扩展演示这个流程:

CREATE EXTENSION vector;

CREATE TABLE docs (
  id        bigserial PRIMARY KEY,
  content   text,
  embedding vector(1536)
);

-- 插入文档(embedding 在应用层算好)
INSERT INTO docs(content, embedding) VALUES
  ('七天无理由换货', '[0.12, -0.04, ...]'),
  ('支持 SAML SSO',  '[0.08,  0.31, ...]');

-- 查询:找跟问题最相近的 5 条
SELECT id, content
FROM docs
ORDER BY embedding <=> '[0.10, -0.02, ...]'::vector
LIMIT 5;

<=> 是 pgvector 的余弦距离操作符(值越小越相近)。这条查询就完成了"按意思找"。pgvector 同样提供 <->(L2 距离)和 <#>(负内积),按需切换。

一个新问题马上冒出来:上面那条 ORDER BY 实际上是把库里每一条文档拿出来跟查询向量算一次距离。库里有 1000 条还好,有 100 万条呢?1536 维的浮点数组算一次余弦距离是几千次乘加,100 万条就是几十亿次。一次查询花上几秒,没法用。

这就是为什么需要专门的"向量数据库"——准确地说,需要一个近似最近邻索引。

向量数据库其实是个 ANN 索引

ANN 是 Approximate Nearest Neighbor 的缩写:近似最近邻。它的工程权衡是这样的——

  • 精确最近邻(线性扫描全库):保证找到最优解,但慢
  • ANN(建索引,按图或聚类跳着搜):不保证一定找到最优解,但能在几毫秒内给出"非常接近最优解"的答案

向量数据库做的最关键的事就是给百万级、千万级、亿级的向量建 ANN 索引。

主流 ANN 索引有两种,入门只需要理解第一种。

HNSW (Hierarchical Navigable Small World) 是 Malkov 和 Yashunin 2016 年的论文。直觉:把所有向量组织成一个分层的图——

  • 顶层只有少量节点,节点之间是"长距离跳跃"的边(像高速公路)
  • 越往下层节点越多,节点之间是"短距离精细"的边(像市区道路)

搜索时先从顶层一个入口节点出发,沿着边走到离查询向量更近的节点;当某层走不动了,下降一层;最底层走完时找到的就是 top-K 近似最近邻。

复杂度从线性扫描的 O(N) 降到 O(log N)。100 万条 1536 维向量的查询通常在 2 到 10 毫秒之间完成。代价是要多用一些内存——HNSW 索引大约要原始向量大小的 1.2 到 2 倍。

HNSW 有三个旋钮需要调:

  • M:每个节点的连边数,典型值 8 到 48。M 越大召回越准,内存越多。
  • efConstruction:建索引时的候选列表大小,典型 100 到 500。越大索引质量越好、建得越慢。
  • efSearch:查询时的候选列表大小,典型 50 到 500。越大召回越好、查询越慢。

调旋钮的本质是在"准 / 快 / 省内存"三件事里取折中。

另一种主流索引是 IVF (Inverted File Index)——先用 k-means 把向量分成若干个簇(nlist),查询时只搜跟查询向量最近的几个簇(nprobe)。原理更简单,配合 Product Quantization (PQ) 可以非常省内存,适合超大规模但能容忍更低召回的场景。入门可以先跳过。

一次完整查询的流程

把上面所有东西串起来。

图 1:一次向量数据库查询的完整流程

flowchart TD
  subgraph 离线索引
    D[原始文档] --> S[切分成片段]
    S --> E1[调 Embedding API<br/>每段变成 1536 维向量]
    E1 --> I[写入向量数据库<br/>建 HNSW 索引]
  end
  subgraph 在线查询
    Q[用户问题] --> E2[调 Embedding API<br/>问题变成 1536 维向量]
    E2 --> A[向量数据库 ANN 检索]
    I -.被检索.-> A
    A --> K[Top-K 最近邻片段]
    K --> R[返回原文给应用层<br/>通常再交给 LLM 生成答案]
  end

注意 embedding 这一步用的是同一个模型——离线索引时把文档转向量用 text-embedding-3-small,在线查询把问题转向量也必须用 text-embedding-3-small。换模型意味着所有向量都得重建。这是工程上一个常见坑。

主流方案速览

入门阶段不用记十个产品,按下面这条路径选就够:

场景 推荐
已有 PostgreSQL、文档百万级以下 pgvector(PostgreSQL 扩展,2023 年 8 月起支持 HNSW)
要全托管、不想运维 Pinecone(2019 年创立,2021 年商用,行业里"默认选项")
要开源、要功能丰富 Milvus(CNCF 项目,HNSW / IVF / DiskANN 都有)
要开源 + 强过滤 + Rust 性能 Qdrant(Rust 写的,filter 支持很好)
Python 原型、本地玩 Chroma(默认本地存储,LangChain / LlamaIndex 教程里最常见)
要 hybrid(稀疏 + 稠密)一站搞定 Weaviate(内置 BM25 + 向量混合检索)

工程上的常见路径:原型阶段用 Chroma 跑通流程,业务上线时落到 pgvector(已有 PostgreSQL)或 Qdrant / Pinecone(专用方案),数据量真上去后再评估 Milvus。

常见疑问

Embedding 模型该选哪个? OpenAI text-embedding-3-small 在 2024 年初是性价比最高的入门选择,每百万 token 只要 0.02 美元,1536 维,效果稳定。如果不想依赖闭源 API,BGE-large 系列(中英文都有)在 MTEB 榜上靠前,本地部署也方便。

一段文本太长怎么办? Embedding 模型有 token 上限——text-embedding-3-small 是 8192,但实际很少有人把整篇长文当一个 chunk。常见做法是先切片(chunking),再对每片做 embedding。

新增文档怎么处理? 向量数据库都支持增量插入——新文档算 embedding 写一条进去就行。但如果你换了 embedding 模型,所有历史向量都需要重新算并替换,这是一个相对重的操作,需要做版本管理。

精度不够 / 召回不准怎么办? 按顺序排查:先看 chunking 切得对不对,再看 embedding 模型选得对不对,最后才是调 HNSW 的 efSearch。绝大多数"召回不准"的问题在前两步就能解决,不是 ANN 索引的锅。

一句话结尾

SQL 是为"等于、包含、范围"设计的;向量数据库是为"按意思找"设计的。把文本变成向量、用 ANN 索引在百万规模里毫秒级找到 top-K 近邻,三件事加起来就是这个新工具的全部基本盘。剩下的复杂度,都是从这三件事衍生出来的。

参考资料

YL

我是姚磊

大模型应用开发者,关注 RAG、Agent 和产品化工程。沉淀实践中的判断、踩坑记录和可复用方法。联系我:yaolei_agent@163.com

LLM AppsRAGProductEngineering Notes