1. 为什么传统数据库搞不定"语义相近"这件事
先从一个最直观的场景说起。你去电商网站搜"能跑马拉松的鞋",传统搜索引擎会老老实实匹配"马拉松""鞋"这些字,结果可能把马拉松周边纪念品也搜出来。但你想表达的其实是"轻量、缓震好、适合长距离的路跑鞋"。如果换成矢量检索,系统会把这句话投射到一个高维空间里的坐标点,然后在这个空间里找到语义上靠近"轻量缓震路跑鞋"的那些商品向量——哪怕商品标题里一个字都没出现"马拉松"。这就是矢量数据库存在的根本理由:它处理的不是字面匹配,而是语义近似。
传统数据库的看家本领是精确匹配和范围查询。SQL里一个WHERE name = '张三'能精确命中,WHERE age BETWEEN 20 AND 30能圈定范围。但现实世界的大量需求是"找相似的""找接近的""按语义去重的",这些需求没法用一个等号或者B+树的区间扫描解决。你当然可以给每条文本做关键词分词再建倒排索引,但词与词之间的同义、上下义关系是词法索引表达不了的。"苹果"和"iPhone"看起来毫无交集,在语义空间里它们却可能相距不远。
这个痛点在我自己接触RAG(检索增强生成)项目时体会更深。最初团队用ES做知识库召回,_query_里写一堆布尔组合、同义词扩展,效果始终差强人意。后来把文档切成段落,用Embedding模型转成向量存进矢量数据库,召回质量立刻上了一个台阶。不是ES不好,而是它做的是"字面命中",矢量数据库做的是"含义命中",两者根本不在一个维度上干活。所以理解矢量数据库,第一条就是要扭转思维:数据不再是一行行的记录、一条条的文本,而是一组组带坐标的高维向量。
2. 嵌入向量到底在存什么
2.1 从词向量到多模态向量
"向量"听起来很抽象,实际上就是一组浮点数。比如把"马拉松"这个单词喂给某个Embedding模型,输出可能是一个384维或者1024维的数组:[0.112, -0.034, 0.507, ...]。这些数字本身没有物理意义,但它们组合在一起,就在高维空间里定义了"马拉松"的位置。
现代Embedding模型的进化路线很有意思。早期是Word2Vec、GloVe这类词向量,每个词一个向量,但解决不了"一词多义"——"苹果"在水果和手机语境里是同一个向量。后来BERT、Sentence-BERT这类上下文模型出现,同一个词在不同句子里可以拥有不同向量。再往后CLIP这类多模态模型直接把图片和文本映射到同一个语义空间,图片和描述它的文字会在空间里靠近。这也是为什么矢量数据库能同时处理图文检索:一切能向量化的模态,都可以进同一个坐标系进行比较。
2.2 为什么行业里总看到384、768、1536这些数字
第一次接触矢量数据库的人都会问:向量维度为什么总是一些奇怪的数字?
- 384:小模型(如
all-MiniLM-L6-v2)的常见输出维度,句子Transformer里常用的MiniLM模型就是384维。 - 768:BERT-base的隐藏层维度,很多开源Embedding模型沿用这一设置。
- 1536:OpenAI的
text-embedding-3-small的输出维度。
维度本质上是"模型认为描述语义所需的独立特征数"。维度越高,理论上能区分的语义细节越多,但存储和计算成本也越高。一个1536维的float向量占6KB内存,一亿条向量就是600GB,单机很难扛住。所以工程上经常做降维或者用Product Quantization(PQ,乘积量化)压缩,后面会细说。
2.3 语义空间里的位置关系
把大量文档切段向量化之后,把这些向量放进一个坐标系(超出三维但道理一样),你会观察到一种现象:语义相近的文本聚集在一起,不同主题的文本在空间中形成一个个"簇"。比如所有关于数据库故障排查的段落聚在一个区域,所有关于前端框架的段落聚在另一个区域。查询的时候,把用户的提问也转成向量,放进这个空间,距离它最近的几个向量就是最相关的文档。
这里有个关键认知:矢量数据库不"理解"你的数据,它只是在数学上维护好这个空间结构,然后用几何距离帮你找邻居。语义之所以能被几何化,完全是Embedding模型的功劳,不是数据库的本事。换句话说,矢量数据库是"空间管理者",Embedding模型才是"语义翻译器"。这两个角色搞混了的人,往往会在项目里踩很大的坑——模型选得不好,换再高级的数据库也救不了召回质量。
3. 相似度度量:余弦、内积、欧氏距离的取舍
向量存好了,怎么判断两个向量"像不像"?主流矢量数据库基本支持三种度量方式:余弦相似度(Cosine Similarity)、内积(Dot Product)、欧氏距离(Euclidean Distance)。
余弦相似度看的是两个向量的方向是否一致,公式是两个向量的内积除以各自模长之积。它只关心方向,不关心长度。这在文本语义场景里很好用,因为文本向量的模长受句子长度和语气影响很大,方向才能体现真正的语义倾向。取值范围在[-1, 1],越大越相似。
内积就是对应维度相乘再求和,它同时把方向、长度都算了进去。在推荐系统里经常用内积,因为用户向量和物品向量的"模长"本身就携带了热度或偏好强度的信息。两个向量即使方向一致,如果模长差很多,内积也不会很高。
欧氏距离是计算高维空间里两点的直线距离,越小越相似。它适合对距离数值敏感的场景,比如图像特征,或者经过归一化处理的向量。
| 度量方式 | 关注点 | 常用场景 | 数值语义 |
|---|---|---|---|
| 余弦相似度 | 方向 | 文本语义检索、RAG | 越大越相似 |
| 内积 | 方向+长度 | 推荐排序、个性化 | 越大越相似 |
| 欧氏距离 | 绝对距离 | 图像特征、归一化向量 | 越小越相似 |
工程选择上有个非常重要的细节:如果选余弦相似度,向量在入库前最好先做L2归一化(把模长变成1)。归一化之后,余弦相似度、内积、欧氏距离三者之间存在转换关系:余弦相似度 = 1 - 平方(欧氏距离)/2,且归一化后内积 = 余弦相似度。很多数据库(比如Milvus)允许你在建Collection时指定metric_type,但底层如果用了某些索引类型,对度量方式有兼容性限制,比如某些版本的HNSW索引对内积和欧氏距离支持得更好,余弦需要在应用层先归一化再转内积。这一点我建议所有人在建索引之前,先翻一遍所选数据库的官方文档"Metric compatibility"部分,否则上线前才发现度量方式不支持,迁移成本极高。
选度量的核心逻辑其实就一句话:先确定你的Embedding分布和业务语义偏好方向,再选度量,最后看索引兼容性。没有人会用余弦相似度去算用户和商品的点击距离,也没人会用内积去算纯文本语义相似度,除非你的向量已经做了归一化。
4. 近似最近邻:为什么"精确搜索"在向量世界走不通
4.1 精确KNN的时间复杂度有多恐怖
如果有10亿条128维向量,要在里面找与某个查询向量最近的10条,精确做法是计算查询向量与全部10亿条向量的距离,然后排序取前10。一次计算,10亿次浮点运算,看起来量级不小但也不算致命;问题是线上每秒有几百上千个查询,每个查询都全量扫一遍,服务器直接原地爆炸。更关键的是性能随数据量线性增长,数据翻倍耗时翻倍,这不是一个可持续扩展的架构。
所以矢量数据库几乎清一色采用ANN(Approximate Nearest Neighbor,近似最近邻)算法。核心思路是:我不保证100%找到全局最近的点,但我用比精确搜索快几个数量级的代价,找到大概率是最近的那一批候选。召回率(Recall)是ANN的关键KPI,比如"Recall@10=95%",意思是查询结果的10个邻居里,平均有9.5个与精确搜索结果一致。
4.2 HNSW:跳表思想在高维空间的延伸
HNSW(Hierarchical Navigable Small World,分层可导航小世界图)是目前落地最广的ANN算法,很多数据库都把它作为默认索引。它的灵感来自一个很著名的图论现象:"六度分隔"——社交网络里看似毫无关联的两个人,通过少量中间人就能建立连接。HNSW把数据点组织成多层图:底层包含全部数据点,上一层是抽样子集,越往上点越少。查询时从顶层开始,贪婪地沿着边走向与查询点最接近的节点,逐层下探到底层,找到一批候选邻居。
HNSW的优点很突出:召回率高,查询延迟稳定,不需要像IVF那样依赖聚类质量。缺点是索引结构是图,内存占用偏高,且每次插入新点需要维护图结构,大批量写入时构建速度比IVF慢。
4.3 IVF:先分区再细查
IVF(Inverted File Index,倒排文件索引)的思路更简单粗暴:先把全部向量用KMeans聚成N个桶,每个桶有一个聚类中心。查询时算出查询点离哪些聚类中心最近,只在这些桶内部做精确扫描,而不是全库扫描。
调参的关键是nlist(桶的数量)和nprobe(查询时扫描的桶数)。nprobe越大,候选范围越大,召回率越高,但延迟也越高。IVF的优点是内存比HNSW小,构建速度快,缺点是召回率对聚类质量敏感,数据分布如果极度不均衡,会出现某些桶数据量过大,拖慢查询。实际项目中常见做法是IVF和HNSW结合场景使用:海量数据、允许一定延迟的离线批量场景用IVF,在线低延迟高召回场景用HNSW。
4.4 PQ:用压缩换速度和内存
Product Quantization(乘积量化)是另一个绕不开的算法,它解决的是"向量太占内存"的问题。做法是把高维向量切成若干段,每段单独做聚类,用聚类中心的编号代替原始浮点数。比如把128维向量切成8段,每段聚类256个中心,那么每个向量只需要8个编号(每编号1字节),总共8字节,相比原始512字节(128维float)压缩了64倍。
PQ的硬核之处在于查询时不需要存原始向量,只要用查表法在压缩后的编码上估算距离,速度极快。代价是精度损失明显,所以工程上常把它和倒排结构结合成IVF-PQ,或者用PQ做压缩、在最后一步用原始向量做重排序,平衡召回率和资源开销。
4.5 为什么精确KNN并没有完全退出
虽然ANN是业界主流,但我仍然会在一些中小规模项目里坚持用暴力精确检索(Brute Force)。当数据量在百万级以下,精确KNN的延迟也就几十到几百毫秒,对内部工具类应用完全可以接受。这时候用精确搜索的好处是结果100%可解释、无需调参、省去索引构建时间。矢量数据库通常都保留"强制扫全量"的接口,比如Milvus里的FLAT索引,本质上就是不做任何加速的精确计算。在大规模系统里做ANN,但不迷信ANN,这是我在选型上的一条原则。
5. 一个向量检索请求的完整旅程
把前面几节串在一起,就能还原一次矢量数据库查询的完整链路。假设你的RAG系统收到用户问题"如何在K8s里排查Pod启动失败",处理流程如下:
5.1 查询入口:过滤条件怎么处理
用户的提问先经过Embedding模型转成查询向量,同时还可能带着一些结构化过滤条件,比如WHERE 时间 > '2024-01-01' AND 标签 = '运维'。矢量数据库的问题在于:向量检索和标量过滤怎么协作?
业界基本有两种路线:先过滤再检索(Filter-then-Search)和先检索再过滤(Search-then-Filter)。
先过滤再检索,会先用标量条件把候选集缩小到合理范围,再对这个子集做向量近邻查找。这样查询准确,但如果过滤后的候选集非常小,召回结果可能不够。
先检索再过滤,会先按向量相似度取出Top-N,再套标量过滤。速度快但可能漏掉"标量条件命中但向量排名靠后"的正确结果。
现代矢量数据库普遍在做的是结合:用标量索引先粗筛出候选集,再在候选集内做向量检索,Milvus 2.x 把这套逻辑封装成"标量过滤+向量检索"的统一计划器。我实际用下来的体会是:过滤字段一定要建标量索引,否则过滤本身会变成全表扫描。不少人只盯着向量索引调参,忽略标量索引,结果检索链路整体延迟居高不下。
5.2 候选集的生成
拿到过滤后的数据子集,查询向量会被送入HNSW图的顶层。算法从顶层节点出发,在当前层寻找与查询向量距离最近的节点,找到后进入下一层,继续找,直到最底层。在底层会维护一个固定大小的动态列表(范围取决于ef参数),里面存当前遇到的距离最近的若干候选节点。遍历完所有可触达的边之后,这个列表就是候选集。
这一步非常考验图的连通质量和ef参数的平衡。ef太小,候选集不够,容易漏掉真正的近邻;ef太大,遍历的节点过多,延迟上去。线上调优的时候,我一般先把ef设到k * 10(k为最终返回条数),再根据延迟和Recall的实测曲线微调。
5.3 重排序与返回
候选集里可能混入一些距离较远但侥幸进入列表的节点。如果底层用的是PQ压缩向量,每个节点存的是压缩编码,距离本身是估算而非精确值。这时候最后一步会做一个精排:把候选集对应的原始向量(如果有存储)捞出来,用精确距离公式重算,按真实距离排序,取最终Top-K返回。
这一步是整个链路里最容易被忽略的优化点。重排序的候选数量通常设置为ef或nprobe结果的数倍,比如k=10时取ef=100,用100条候选里选出10条精确最优。对海量数据场景,只把Top-K的原始向量放到内存里做精确计算,开销极小但召回率提升明显。这也是为什么很多高端配置都会强调"保留原始向量用于重排"。
6. 索引参数调优:M、efConstruction、ef到底怎么设
HNSW作为默认王牌索引,参数理解透了,基本就掌握了大半调优技能。核心参数有三个:
M:构建图时每个节点的最大连接数。M越大,图越稠密,查询时路径越短,召回率越高,但内存占用和构建时间也越大。常见的起步值是16或32。我自己的经验:M设为16在千万级数据上通常能平衡好延迟和召回,如果内存充足且延迟敏感,可以考虑提到32。
efConstruction:构建索引时使用的动态候选列表大小。它越大,构建期间会花更多力气寻找合适的邻居,图的质量越高(连接更接近"真近邻"),构建时间也越长。一般取值在100到500之间。这里有个容易踩的坑:efConstruction和查询时的ef没有直接关系,但增大了efConstruction并不能让查询更快,它只会让图更"可靠"。很多人把两个参数搞混,调了半天查询性能没变化,原因就在这。
ef(查询参数):查询时动态候选列表大小,这个值是查询时可以实时调整的。ef越大,候选越充分,召回越高,延迟也越高。开箱推荐值是2*k到10*k,上线压测时再用如recall@10作为指标逐档测试。
调参方法论,我总结为四步:
- 先固定数据量级和一个可接受延迟上限,比如P95 < 50ms。
- 用召回率脚本(打一批已知近邻的查询)测试不同M和efConstruction的离线索引质量。
- 固定M和efConstruction,扫描ef从20到200,画出"延迟-Recall"曲线。
- 选出曲线拐点的值作为线上配置,并在查询接口留一个ef的动态参数,方便后续按流量调整。
内存估算也是必做的功课。HNSW每个向量的内存开销大约为维度 × 4字节 × (1 + M),你在设计容量时按这个公式先粗算。比如128维、M=16,每个向量约8.7KB,1000万条就是87GB,单机扛不住就得考虑分片或用PQ压缩。千万级数据上线前不算内存,等你创建Collection发现OOM再回头改配置,那个过程相当痛苦。
删除与更新更是隐蔽的坑。矢量数据库的删除普遍是"墓碑标记":标记删除后,向量并不会立刻从索引结构里移除,而是等后台Compaction真正清理。查询时墓碑会被跳过,但索引体积不会立刻减小,写入性能也可能被Compaction任务短暂拖累。如果你的业务频繁更新向量(比如商品特征每天变更),务必选择支持异步Compaction并且可手动触发Compaction的数据库。我见过有团队频繁执行delete + insert来更新向量,结果索引体积疯涨,查询延迟飙升,最后只能全量重建索引才能恢复。
7. 矢量数据库与传统数据库的架构差异
7.1 存储引擎:向量和标量各居其位
传统关系数据库的存储引擎围绕B+树设计,数据按主键有序组织,适合范围扫描与点查。矢量数据库的存储体系则通常分为两部分:向量数据走专门的索引结构(HNSW图、IVF倒排),标量元数据走类LSM或B+树的索引。两者并行维护,查询时通过计划器协同工作。这种双轨制意味着矢量数据库的写入链路比传统数据库复杂:一条记录既要写标量存储,又要更新向量索引,事务和一致性模型因此天然比传统数据库弱。
7.2 扩展能力:分片和分区策略
水平扩展上,传统数据库常用分库分表,按主键或区间划分数据。矢量数据库的分片逻辑通常按哈希或者按业务分区键,把不同向量集合分到不同节点。更关键的是它没有全局索引:查询时要么广播到所有分片做并行ANN搜索再合并结果,要么通过分区做裁剪把查询定位到少量分片。前者实现简单但放大查询开销,后者省资源但对业务数据分布要求高。用Milvus的时候,给每个租户设计独立的Collection或者按租户ID做分区,就是在用分区裁剪换取隔离性和查询性能。
7.3 混合搜索:矢量不是万能的
说句公道话,矢量检索在"精确条件"上弱得离谱。你没法用向量直接表达"价格严格小于100元""状态等于已发布",这些逻辑还得靠标量过滤。所以成熟系统里矢量数据库几乎从来不是单打独斗,它跟前置的过滤条件、后置的规则引擎、甚至传统数据库协同工作。RAG系统尤其如此:先从矢量数据库召回Top-K候选,再用规则或重排序模型精排,最后交给大模型生成。矢量和标量是互补关系,不是替代关系。想用矢量数据库取代业务系统的MySQL,方向就错了。
8. 矢量数据库实际解决的核心问题
聊完底层机制,回到真实的业务场景。我从自己的项目经验里挑四个典型问题,说明矢量数据库到底在哪些地方不可替代。
8.1 语义搜索与文档召回
这是最主流的应用,也是我最早接触矢量数据库的契机。传统关键词搜索遇到用户用口语化描述、同义改写、跨语言提问时基本失灵。语义搜索把查询和文档都向量化,用户的"怎么让服务器别老宕机"能命中文档里的"高可用架构与故障恢复",哪怕字面毫无交集。文本切分粒度是这里最影响效果的因素:切太碎丢上下文,切太长引入噪音,常用策略是300-500字一段,加上相邻段落的重叠窗口。
8.2 RAG(检索增强生成)
RAG是大模型应用落地的主力架构。大模型的参数知识有截止日期、容易幻觉,RAG通过把外部知识库的相关片段注入上下文,让模型生成时有所依据。矢量数据库是RAG的召回底座:知识库切片向量化入库,用户提问向量化去检索,把Top-K片段拼进Prompt。这里的核心指标不只是延迟,还有检索结果的集中度——如果Top-5结果来自相互矛盾的文档,大模型会生成一段自相矛盾的内容。我最近在做的项目里就加了一道"结果去重+话题一致性校验",比单纯调索引参数有效得多。
8.3 多模态检索
借助CLIP这类多模态模型,图片、音频、视频都能编码进同一语义空间。上传一张"红色跑鞋"的图,能检索出所有"红色跑鞋"的图片,不依赖图片的命名或标签。这在商品推荐、内容去重、素材管理的场景里是刚需。
8.4 推荐系统与大规模去重
推荐系统里,用户向量和物品向量做内积排序是实现个性化召回的高效路径。内容平台上做相似稿件去重,把文本、图片向量化后找高相似对,也能显著节省人工审核成本。这类场景对写入吞吐有要求,矢量数据库流式写入的能力就比离线批处理方案有优势。
矢量数据库远不是什么万能银弹,它的定位很清楚:为高维向量提供高效存储、索引和检索的专用基础设施。选型时先想清楚自己的数据能否向量化、Embedding模型是否足够可靠,再考虑用哪种索引、什么硬件的预算。我在实操里的体会是,它真正吃透价值的场景,永远是那些语义相关、海量数据、延迟敏感的检索系统。把这三点想透了,你就能判断该不该上矢量数据库、该在什么环节引入它,而不会把它变成又一个吃内存的玩具。