你在做 RAG 或者语义搜索,看了一圈向量数据库的介绍,发现都在讲「支持多少亿向量」「毫秒级检索」,但没人说清楚它底层到底怎么存、怎么找。于是选型只能看参数表,上线后才发现召回率不对、内存爆了、扩副本也没用。这篇从存储结构、索引类型、检索流程三个层面把原理讲透,再给一套你能自己验证的压测步骤。
你在做 RAG 或者语义搜索,翻了一圈向量数据库的介绍,满屏都是「支持多少亿向量」「毫秒级检索」,可没人认真说清楚它底下到底怎么存、怎么找一个「差不多的邻居」。于是选型只能对着参数表比大小,上线之后才发现召回率不对、内存爆了、加副本也没救回来。这篇不讲概念史,直接从存储结构、索引路线、检索流程三层把原理摊开,再给你一套能自己跑的压测步骤,看完至少知道该往哪问、该测什么。
先说清楚它的定位:向量数据库不是「更快的数据库」,它的核心任务是把「找最相似」这个操作从全量比对变成近似查找,用一点点精度换大幅度的速度。所以理解它的关键不是记住几个索引名字,而是搞清楚它在哪一步做了妥协、妥协换来了什么、什么条件下这个妥协不划算。下面每一节都配了可执行的判断动作,你可以在选型会上直接拿去问供应商。
为什么不能暴力比对:先把问题定义清楚
暴力检索就是把查询向量和库里每一条都算一遍相似度,然后排序取前几名。这件事在数据量还小的时候快得让你怀疑为什么需要向量库。真正的问题出在规模和内存带宽上:每多一条数据,每次查询就多一次完整的距离计算,而且这些计算要反复把向量从内存搬进 CPU,瓶颈往往不在算力而在搬运。
- 几万条以内、查询频率不高:先用普通数据库加向量扩展就能扛,别为了「技术先进」提前引入新组件
- 百万级以上、且查询有并发:这时候索引带来的收益才明显,因为暴力比对的耗时是随数据量线性涨的
- 要分清「存向量」和「查向量」是两个问题:很多方案把存做好了,查询还是靠暴力,等于只解决了一半
- 不适用的情况:数据量极小、写入比查询频繁得多、或者业务对召回要求接近百分之百,这时候索引的构建和维护开销反而是纯负担
底层怎么存:向量、主键和元数据是三套东西
向量数据库内部通常不是一个统一的大结构,而是几套东西拼起来的。向量本体一般按定长浮点数组连续存放,因为维度是固定的,连续排布才能让 CPU 用批量指令一次算好几个。而主键、标签、时间、来源这类元数据,走的是另一套结构,常见的是倒排索引或者列式存储,专门用来做条件过滤。
这两套东西怎么配合,直接决定带条件查询的结果对不对。理想情况是先把过滤条件推到索引层,只在你允许的数据范围内找邻居;差一点的情况是先按向量找出候选,再用条件筛,候选集里符合条件的太少就会召回不足,表现出来就是「明明库里有这条数据却搜不出来」。
- 连续存放
- 向量按固定维度连续排布,目的是让批量相似度计算更快,代价是维度固定、不好变长
- 元数据索引
- 主键和标签走倒排或列存,用来在检索前缩小范围,和向量本体是两套结构
- 过滤下推
- 把过滤条件下推到向量索引内部执行,而不是查完再筛,这是带条件检索效果好坏的分水岭
落地动作很简单:拿你真实的查询语句去测一次带条件的检索,看返回条数是不是明显少于不带条件时的候选数。如果差得离谱,先去看文档里过滤是怎么执行的,而不是急着调索引参数。各家实现差异不小,站内对同类方案的整理可以去 /sites 对照看。
索引的几条技术路线:各自在赌什么
所有近似索引都在做同一件事:想方设法少算几次距离。区别在于它们假设数据有什么样的结构、愿意为此付出什么代价。记住这个视角,比背名字有用得多。
| 路线 | 基本思路 | 换来了什么 | 代价 |
|---|---|---|---|
| 图结构类 | 每个向量连若干近邻,查询时在图上跳转 | 查询快、召回通常较好 | 内存占用高、构建慢、更新成本高 |
| 量化压缩类 | 把向量压成很短的编码,用编码估距离 | 内存占用大幅下降,能装更大的库 | 有精度损失,召回要实测 |
| 聚类分桶类 | 先粗分若干桶,查询只进少数几个桶 | 结构简单、内存可控 | 桶数量等超参要调,边界样本容易漏 |
| 精确暴力 | 不做近似,全量比对 | 召回有保证 | 规模一上去延迟就不可接受 |
怎么挑不靠感觉,靠约束条件。如果内存预算充足、并发高、对召回敏感,图结构类通常更稳;如果内存紧张、能接受一定精度损失,量化路线更合适;如果数据分布集中、查询模式单一,分桶类调好参也能用。
- 先明确内存上限,再看哪些方案放得下,放不下的一律不考虑
- 再明确召回底线,用你自己的数据测,不看宣传材料
- 最后看写入和删除模式,更新频繁的场景要特别关注索引的维护成本
- 不适用的情况:查询量很低但精度要求极高时,构建图索引的时间和内存可能永远收不回来,老老实实用精确检索更划算
一次查询走完哪几步
把一次查询拆成流水线,排查问题的时候就知道该从哪一段下手。第一步永远是文本过嵌入模型变成向量,这一步的模型必须和当初建库时用的是同一个。换了模型,向量空间就变了,旧数据和新查询不在一个坐标系里,结果会静默地变差,不报错但召回崩。
- 确认嵌入模型一致核对建库和查询用的是不是同一个模型。换了模型就得整体重建,没有廉价补救办法。查出问题先看这一步,再看别处
- 看粗筛候选集大小索引不会在全库里精算,而是先取一批候选再算真实距离。候选集大小直接决定了召回的上限,调小会明显掉召回,调大则延迟上升
- 核对距离度量余弦、内积、欧氏距离算出来的分数含义不同,不能跨方案直接比大小。换库迁移时要确认度量的语义是否一致,否则阈值全废
- 最后才动索引参数召回不对时,先排除模型和过滤问题,再怀疑索引参数。顺序搞反了会在参数上白折腾好几天
还有一点容易被忽略:返回的距离分数是相对的,不同数据分布下同一个分数代表的意义不一样。所以判断「这条结果算不算相似」不要硬编码一个阈值,而是拿一批标注样本来定。具体到不同方案的实际表现,可以到 /rankings 看当时的对比数据。
过滤条件与混合检索:最容易踩坑的一段
纯向量检索有个天然短板:它对专有名词、产品编号、缩写这类需要精确匹配的东西不敏感。因为这些词在嵌入空间里可能挨得很近,模型分不清具体是哪一个。混合检索就是把向量检索和关键词检索结合起来,用关键词补精确匹配的短板,用向量补语义泛化的短板。
过滤条件的坑更隐蔽。很多实现默认是「先检索后过滤」,也就是先在全库找邻居,再拿元数据把不符合的删掉。如果过滤条件很严格,比如某个租户的数据只占全库很小一部分,那候选集里绝大部分会被筛掉,最终返回寥寥几条,你以为是数据没进去,其实是检索路径的问题。
| 场景 | 推荐做法 | 验证方式 |
|---|---|---|
| 条件严格、选择性高 | 要求过滤下推,在索引内限定范围再找邻居 | 对比带条件和不带条件时的返回条数差异 |
| 条件宽松、几乎全覆盖 | 先检索后筛通常可接受 | 看尾延迟和召回是否稳定 |
| 既要语义又要精确匹配 | 混合检索,向量和关键词分别召回再融合 | 用专有名词构造查询,看能不能命中 |
| 多租户隔离 | 确认分片或过滤是否按租户切分 | 跨租户查询是否能被有效限制 |
带条件的向量检索出问题,九成不是索引参数没调好,而是过滤发生的位置不对。
判断标准可以很简单:你的业务查询里如果带着大量精确条件,比如只能查某个组织、某段时间的数据,那过滤下推能力应该排在选型评估的前面,而不是等部署完再发现。如果数据量本来就小、条件也很简单,那这块差异不大,不用过度纠结。
内存、更新与扩容:选型真正分叉的地方
图结构类索引基本需要把向量和邻居关系常驻内存,库一大,内存就是主要成本项,而且这个成本跟着数据量走,不是你多加几块盘能解决的。量化压缩能把内存压下来,但压缩比和召回率是一对矛盾,压缩得越狠,结果越不准。这个取舍必须拿你自己的数据实测,宣传材料上的数字参考价值有限。
更新和删除的处理方式差别也很大。有的实现删除只是打个标记,空间不会立刻归还,看着数据删了一半内存却没降。有的实现插入会触发索引重建或局部重排,写入延迟会有毛刺。扩容之前先搞清楚分片是按什么切的,是按主键、按租户还是按向量聚类,切错了会导致查询打到多个分片,尾延迟明显变高。
- 先算纯向量内存下限,再叠加索引自身的开销,留出余量
- 确认删除是真释放还是打标记,长期跑的服务这条影响很大
- 测写入延迟的分布,不只看平均值,关注毛刺频率
- 确认分片键的选择,以及查询是否会跨分片
- 拿不准的容量数字,到 /price-watch 或站内算力与规格页面核对当时的行情
自己动手验证:一套可复用的压测流程
向量库的选型靠听讲解没用,必须自己跑一遍。下面这套流程不复杂,但能帮你把「参数表上看不出来」的问题提前暴露出来。核心原则是一次只改一个变量,否则出了问题不知道是谁的锅。
- 准备带标准答案的数据集从真实业务里抽一个子集,人工标注一批查询的正确结果。没有标注就没法算召回,只能凭感觉说「好像还行」,这种结论没有价值
- 固定嵌入模型和度量在整个压测过程中不要更换嵌入模型和距离度量,只调索引相关参数,一次只动一个
- 记录三组数召回率、单次查询延迟的分布(看分位数不只看平均)、内存占用峰值。三个一起看,只看其中一个容易被误导
- 单独测带条件的场景用你业务里最严格的那类过滤条件再跑一遍,对比不带条件时的返回差异
- 把结论和前提一起存档写下当时的嵌入模型、数据规模、参数设置。模型一换,之前的结论基本作废,所以要记清楚前提
如果你不想自己从零搭环境,站内的速度测试工具可以先用一轮粗筛,快速排除明显不行的方案,再对留下的一两个做精细压测。工具入口在 /tools/speed-test。注意工具给的是通用场景下的表现,你自己的数据和查询分布可能完全不同,只能当筛子用,不能当结论。
选型对照表:按你的约束条件挑
把前面几节的判断收拢成一张表,选型时按自己的约束从上往下过。关键是想清楚你最怕的是什么:怕运维复杂、怕内存超支、还是怕召回不够。
| 你的约束 | 优先考虑 | 要先验证什么 |
|---|---|---|
| 数据量小、想少运维 | 已有数据库自带的向量扩展 | 扩展的检索能力是否够用,避免多引组件 |
| 数据量大、并发高 | 图结构类索引方案 | 内存成本能不能接受,构建时间是否可忍受 |
| 内存预算紧、可容忍精度损失 | 量化压缩路线 | 用自己数据实测召回,别信默认参数 |
| 强过滤、多租户隔离 | 支持过滤下推的方案 | 严格条件下的召回是否稳定 |
| 更新极频繁、查询很少 | 精确检索或简单结构 | 索引维护开销是否超过收益 |
最后提醒一句不适用的情况:上面这些判断都建立在你已经有明确的查询模式和可标注的评测集上。如果连自己的查询长什么样都还没摸清,先别选型,先把业务侧的查询日志捞出来看一遍,那比读十篇对比文章都有用。
下一步拿你手上真实的一小批数据和查询,照着压测流程跑一轮,把召回、延迟分布、内存峰值三个数记下来;同时到站内模型资料库和价格追踪页核对当时的规格与行情,两者对上了再定方案。→常见问题
向量数据库和普通数据库加个向量字段有什么区别?
小规模场景区别不大,普通数据库加扩展也能跑。区别在数据量上来之后:专用向量库的索引结构是为近似检索设计的,能在毫秒级筛掉绝大多数不相关的向量;普通数据库通常还是全量比对,规模一大延迟就线性上涨。先用小数据验证,别为想象中的规模买单。
近似检索会不会漏掉正确结果?
会,这是设计上的取舍,不是 bug。近似索引用精度换速度,所以召回率是个需要实测的指标,而不是开关。控制手段是调大候选集、调索引参数,代价是延迟和内存上升。如果你的业务不能容忍漏召回,就得把候选集开到很大,本质上退化成接近暴力检索。
换嵌入模型需要重建整个库吗?
需要。不同嵌入模型生成的向量空间不可比,同一个文本在两个模型下的向量算距离是没有意义的。所以嵌入模型一旦确定,要么整个库重建,要么新旧模型各存一份、查询时路由到对应索引。这也是为什么选型时要把嵌入模型的稳定性一起纳入考量。
向量维度越高是不是效果越好?
不一定。维度高能表达更多信息,但存储和计算成本同步上升,而且在数据量不足时高维反而更容易出现距离区分度下降的问题。实际做法是拿几个候选嵌入模型在自己的数据上跑召回对比,别只看维度这个数字,具体模型资料可以到站内嵌入模型页面核对。
自建向量库和用托管服务怎么选?
看团队有没有人能长期维护这套东西。自建的好处是数据不出内网、成本可控,代价是扩容、备份、索引重建都得自己扛。托管省事但有网络延迟和数据合规的额外考虑。中间路线是在已有数据库上加向量扩展,运维面最小,适合人手紧的团队。