数据库基础体系 · 第 59/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

向量数据库基础:Embedding、距离度量、召回、过滤与一致性

向量数据库用于保存和检索向量。向量本身只是一个由浮点数或其他数值组成的数组,真正决定检索效果的,是以下几层共同作用:

  1. Embedding 模型把文本、图片或其他对象映射为向量;
  2. 距离度量定义两个向量“相似”的含义;
  3. 检索算法从数据集中找到距离最近的候选;
  4. 召回描述近似检索相对于精确结果找回了多少;
  5. 过滤把向量相似度与结构化条件结合起来;
  6. 一致性决定写入、更新和删除何时能被查询看到。

这些概念彼此相关,但不能互相替代。例如,换一个距离函数不会自动提高召回率;提高 ANN 索引的搜索参数也不会解决数据尚未可见的问题。


一、向量数据库到底保存什么

一条典型的向量数据至少包含四类信息:

主键:document-001
向量:[-0.12, 0.38, ...]
结构化字段:tenant_id = 42, language = "zh", year = 2024
原始内容或引用:文章 URL、分块文本、对象存储路径

向量通常不是原始数据的替代品,而是原始数据的检索表示。生产系统往往保存:

  • 原文或原始对象;
  • 文档分块后的文本;
  • Embedding 模型名称和版本;
  • 分块策略版本;
  • 租户、权限、时间、语言等元数据;
  • 向量数据库中的主键和索引状态。

这样做很重要,因为向量是模型输出,不是稳定的业务事实。模型升级、分块方式变化或预处理方式变化后,即使同一段文本也可能生成不同维度或不同语义空间的向量。

1. Embedding 的定义

Embedding 是一个映射函数:

f:XRdf: X \rightarrow \mathbb{R}^d

其中:

  • XX 是输入对象空间,例如文本集合;
  • dd 是向量维度;
  • f(x)f(x) 是对象 xx 的向量表示。

例如,一个文本编码模型可能把一句话映射为 768 维向量:

"数据库事务保证一致性"
        ↓
[0.13, -0.42, 0.08, ..., 0.27]

Embedding 的目标通常不是让字面相同的文本拥有相同向量,而是让某种任务下“相关”的对象在向量空间中更接近。

因此,Embedding 不是通用的“语义坐标系”。以下向量通常不能直接混用:

  • 不同模型生成的向量;
  • 同一模型的不同版本;
  • 文档编码向量与查询编码向量不匹配的模型;
  • 使用不同文本预处理规则生成的向量;
  • 训练目标不同的通用模型和领域模型。

如果文档向量使用模型 A,查询向量使用模型 B,即使两者维度相同,距离数值也通常没有可比性。维度相同只说明数组形状相同,不说明坐标语义相同。

2. 文本分块会改变检索结果

对长文档直接生成一个向量,可能把多个主题压缩到一个点中。常见流程是:

原文
  ↓
按标题、段落或 token 数分块
  ↓
为每个分块生成 Embedding
  ↓
写入向量数据库,并保存 document_id、chunk_id 和原文引用

分块太大,单个向量包含多个主题,查询时区分度下降;分块太小,上下文不完整,召回的片段可能无法回答问题。这个问题不能通过增加 HNSW 的搜索参数解决,因为索引只能在已有向量中搜索。


二、距离度量:什么叫“最近”

向量数据库通常执行 Top-kk 近邻搜索:

TopK(q,D,d)\operatorname{TopK}(q, D, d)

表示在数据集 DD 中,按照距离函数 d(q,x)d(q,x) 排序,返回距离最小的 kk 个向量。

不同距离函数会产生不同的排序。选择距离函数不是数据库索引的装饰配置,而是检索语义的一部分。

1. 欧氏距离

欧氏距离定义为:

dL2(q,x)=i=1d(qixi)2d_{\text{L2}}(q,x) = \sqrt{\sum_{i=1}^{d}(q_i-x_i)^2}

它表示向量空间中的直线距离。平方根不影响排序,因此实现中常使用平方欧氏距离:

dL22(q,x)=i=1d(qixi)2d_{\text{L2}}^2(q,x) = \sum_{i=1}^{d}(q_i-x_i)^2

优点是直观,适合模型明确使用欧氏空间训练的场景。缺点是它同时受方向和长度影响。

2. 内积

内积定义为:

qx=i=1dqixiq \cdot x = \sum_{i=1}^{d}q_i x_i

如果向量长度没有归一化,内积既反映方向,也反映长度。对于最大内积搜索,内积越大越相似。

以 PostgreSQL 的 pgvector 为例,<#> 运算符返回的是负内积,原因是 PostgreSQL 索引扫描通常按升序处理距离:

embedding <#> query_vector

数值越小,实际内积越大。因此不能把 pgvector<#> 返回值直接当作普通正向相似度。

3. 余弦相似度与余弦距离

余弦相似度是:

cos(q,x)=qxqx\operatorname{cos}(q,x) = \frac{q\cdot x}{\|q\|\|x\|}

其中:

q=iqi2\|q\|=\sqrt{\sum_i q_i^2}

余弦距离通常定义为:

dcos(q,x)=1cos(q,x)d_{\text{cos}}(q,x)=1-\operatorname{cos}(q,x)

它只关注方向,不关注向量长度。文本语义搜索中常用余弦度量,因为向量长度有时更多反映文本形式或模型内部置信度,而不是语义方向。

4. 归一化后的等价关系

如果 qqxx 都被归一化为单位向量:

q=x=1\|q\|=\|x\|=1

则:

qx2=(qx)(qx)=q2+x22qx=22(qx)\|q-x\|^2 = (q-x)\cdot(q-x) = \|q\|^2+\|x\|^2-2q\cdot x = 2-2(q\cdot x)

而单位向量的内积就是余弦相似度,因此:

  • L2 距离越小;
  • 内积越大;
  • 余弦距离越小;

三者会产生相同的排序。

但是,只有在所有向量都已归一化时,这个结论才成立

5. 完整算例

令查询向量:

q=(1,0)q=(1,0)

四个候选向量为:

a = (1, 0)
b = (0.8, 0.6)
c = (0, 1)
d = (-1, 0)

其中 aabb 都是单位向量。

向量 内积 qxq\cdot x 余弦相似度 L2 距离
a 1.0 1.0 0
b 0.8 0.8 0.632
c 0.0 0.0 1
d -1.0 -1.0 2

这里三种度量给出相同顺序。

再加入:

e = (10, 0)

此时:

  • 内积:qe=10q\cdot e=10
  • 余弦相似度:11
  • L2 距离:99

如果任务关注方向,ea 的余弦相似度相同;如果任务关注欧氏位置,eq 很远;如果直接使用内积,e 会因为长度大而排在 a 前面。

这就是“度量选择错误”的典型表现:检索结果并非数据库算错,而是排序标准与模型或业务目标不一致。

6. pgvector 中的距离运算符

pgvector 文档中常见的运算符包括:

运算符 含义
<-> L2 距离
<#> 负内积
<=> 余弦距离
<+> L1 距离

使用哪一个运算符,必须与创建索引时的 operator class 一致。例如:

CREATE INDEX documents_embedding_hnsw_cos_idx
ON documents
USING hnsw (embedding vector_cosine_ops);

查询余弦距离:

SELECT
    id,
    content,
    embedding <=> '[0.2, 0.1, 0.7]'::vector AS distance
FROM documents
ORDER BY embedding <=> '[0.2, 0.1, 0.7]'::vector
LIMIT 5;

这里按升序取距离最小的五条记录。若需要显示余弦相似度,可以使用:

SELECT
    id,
    1 - (embedding <=> '[0.2, 0.1, 0.7]'::vector) AS cosine_similarity
FROM documents
ORDER BY embedding <=> '[0.2, 0.1, 0.7]'::vector
LIMIT 5;

这个转换只适用于这里定义的余弦距离语义,不能把 L2 距离或负内积也机械地写成 1 - distance


三、精确搜索、近似搜索与召回

1. 精确 Top-kk

精确搜索会计算查询向量与所有候选向量的距离,然后排序或维护一个 Top-kk 集合。

数据量为 NN,向量维度为 dd 时,朴素计算大致需要 O(Nd)O(Nd) 的距离计算。它的优点是结果可作为标准答案,缺点是数据量大时延迟和 CPU 成本较高。

在 pgvector 中,不创建近似索引时,可以通过顺序扫描执行精确搜索:

SELECT id, content
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> $1
LIMIT 10;

这里的 $1 是参数化传入的查询向量。对于规模较小的数据集,精确搜索常常比维护复杂 ANN 索引更简单。

2. 近似最近邻

近似最近邻(ANN)不保证返回全局真实最近的向量,而是通过索引减少需要比较的候选数量。

常见方法包括:

  • Flat:扫描全部向量,通常是精确搜索;
  • HNSW:使用多层图快速导航;
  • IVF:先选择若干聚类中心,只搜索部分簇;
  • PQ:压缩向量或距离计算,节省内存,但通常引入误差。

这些方法的细节属于向量索引原理,但对本文主题有一个关键影响:ANN 的结果质量由“候选集是否包含真实近邻”决定。

3. 召回率的定义

给定查询 qqkk

  • Gk(q)G_k(q):使用精确搜索得到的真实 Top-kk
  • Ak(q)A_k(q):近似索引返回的 Top-kk

常用的 Recall@k 为:

Recall@k=Gk(q)Ak(q)k\operatorname{Recall}@k = \frac{|G_k(q)\cap A_k(q)|}{k}

例如精确结果为:

G5 = {a, b, c, d, e}

ANN 结果为:

A5 = {a, b, c, x, y}

交集有三个元素,因此:

Recall@5=35=0.6\operatorname{Recall}@5=\frac{3}{5}=0.6

这不是“返回结果中有 60% 相关”的主观评价,而是相对于精确 Top-kk 的集合重合率。

实际评估时,应在代表性查询集上统计 Recall@k,而不是只看单条查询。还要固定:

  • Embedding 模型;
  • 距离度量;
  • 数据集快照;
  • 查询过滤条件;
  • Top-kk
  • 索引参数;
  • 是否包含尚未稳定的写入数据。

4. 召回率与相关性不是同一个概念

召回率只衡量 ANN 是否找回精确算法认为最近的向量。如果 Embedding 模型本身没有表达正确语义,精确搜索也可能返回业务上不相关的结果。

因此存在两种不同失败:

模型表达错误:
语义相关对象根本没有靠近,精确搜索也找不到。

ANN 近似误差:
语义相关对象已经靠近,但索引没有把它找出来。

前者应检查模型、文本清洗和分块;后者应调整索引构建或搜索参数。


四、过滤:向量相似与结构化条件如何结合

向量搜索通常不是“全库找最相似”,而是:

在满足业务条件的记录中,找向量最相似的 Top-k

形式化表示为:

TopKxD,  P(x)=trued(q,x)\operatorname{TopK}_{x\in D,\;P(x)=\text{true}} d(q,x)

其中:

  • DD 是全部向量;
  • P(x)P(x) 是过滤谓词,例如租户、权限、语言或时间;
  • 只对满足 P(x)P(x) 的向量进行 Top-kk 排序。

1. 后过滤为什么会返回不足 k 条

假设全库按距离排序为:

A(0.10), B(0.12), C(0.15), D(0.20), E(0.22)

过滤条件为:

tenant_id = 42

其中只有 AE 属于租户 42。

如果系统先取全库 Top-3,再执行过滤:

全库 Top-3:A, B, C
过滤后:A

结果只有一条,而不是满足条件集合中的 Top-3。即使把候选扩大到 Top-5,也只能在候选足够覆盖过滤集合时得到正确结果。

因此,以下两种语义不同:

先取全库 Top-k,再过滤

与:

先限定候选集合,再取 Top-k

生产系统必须确认使用的是哪一种。

2. 过滤策略的三种形态

预过滤

先使用结构化索引筛选候选,再执行向量搜索:

tenant_id = 42 AND language = 'zh'
        ↓
候选集合
        ↓
向量 Top-k

优点是语义正确且适合高选择性条件;缺点是候选集合组织复杂,过滤条件不选择时收益有限。

后过滤

先执行 ANN,再在结果中应用过滤条件。

优点是实现简单;缺点是过滤条件严格时容易返回不足、结果偏差大,甚至无法得到真实的过滤集合 Top-k。

过滤感知的 ANN

把标量过滤条件纳入 ANN 搜索过程,在图遍历、分区选择或候选扩展时跳过不满足条件的实体。

这通常是系统实现中的折中方案:比简单后过滤正确得多,但其性能和结果仍取决于索引结构、过滤选择性和搜索参数。

pgvector 的 SQL 语义可以明确写成:

SELECT
    id,
    content,
    embedding <=> $1 AS distance
FROM documents
WHERE tenant_id = $2
  AND language = 'zh'
ORDER BY embedding <=> $1
LIMIT 10;

这条 SQL 的关系语义是先限定 WHERE 条件,再在符合条件的行上排序。不过是否使用近似向量索引、过滤发生在索引扫描的哪个阶段,则由 PostgreSQL 查询规划器、索引类型和 pgvector 版本能力共同决定。应使用 EXPLAIN (ANALYZE, BUFFERS) 验证,而不能仅凭 SQL 外观判断执行路径。

3. 过滤选择性会改变索引取舍

设过滤后只有全库的 0.01%0.01\%

  • 如果索引能有效利用过滤条件,搜索空间大幅缩小;
  • 如果 ANN 先找少量候选再后过滤,候选很可能全部被过滤掉;
  • 如果为了补足结果而反复扩展候选,延迟可能突然上升;
  • 如果过滤字段是租户隔离条件,错误的后过滤还可能带来数据越权风险。

tenant_id、权限范围等条件不能只当作性能过滤器。它们通常是结果正确性和安全边界的一部分。

4. 过滤字段的类型边界

结构化过滤字段应使用数据库可索引的类型,例如:

  • 整数或字符串租户标识;
  • 时间戳;
  • 布尔状态;
  • 标量数组或集合;
  • 经过明确建模的标签字段。

不要把所有元数据都序列化成一段 JSON 文本后再依赖模糊字符串匹配。这样会使过滤语义、索引能力和类型检查都变得不明确。


五、pgvector 中的一个完整基础示例

以下示例以 PostgreSQL 加 pgvector 为边界。它展示的是小型数据集上的基本流程,距离是余弦距离,事务语义由 PostgreSQL 提供。

1. 建表和写入

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE documents (
    id          bigserial PRIMARY KEY,
    tenant_id   bigint NOT NULL,
    language    text NOT NULL,
    content     text NOT NULL,
    embedding   vector(3) NOT NULL,
    model_name  text NOT NULL,
    created_at  timestamptz NOT NULL DEFAULT now()
);

INSERT INTO documents
    (tenant_id, language, content, embedding, model_name)
VALUES
    (42, 'zh', '事务提供原子性和隔离性', '[1,0,0]', 'demo-v1'),
    (42, 'zh', '向量检索按照距离返回近邻', '[0.8,0.6,0]', 'demo-v1'),
    (42, 'en', 'Transaction isolation levels', '[0,1,0]', 'demo-v1'),
    (7,  'zh', '另一个租户的数据库文章', '[0.9,0.1,0]', 'demo-v1');

前置条件是所有向量必须有相同维度 3,并且由同一个示例模型空间产生。真实系统不应把不同模型的向量写入同一逻辑检索空间,除非有明确的模型分区和查询路由。

2. 精确过滤检索

SELECT
    id,
    content,
    embedding <=> '[1,0,0]'::vector AS cosine_distance
FROM documents
WHERE tenant_id = 42
  AND language = 'zh'
ORDER BY embedding <=> '[1,0,0]'::vector
LIMIT 2;

预期结果按距离升序大致为:

事务提供原子性和隔离性       0
向量检索按照距离返回近邻       0.2

tenant_id = 42 保证不会返回租户 7 的记录,language = 'zh' 排除英文记录。这里的结果是过滤集合内的精确 Top-2,前提是数据库执行的是精确排序而不是近似索引路径。

3. 创建 HNSW 索引

数据量增大后,可以创建 HNSW 索引:

CREATE INDEX documents_embedding_hnsw_cos_idx
ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

其中:

  • m 控制图中连接数量,影响索引大小、构建成本和搜索质量;
  • ef_construction 影响构建阶段候选搜索范围;
  • vector_cosine_ops 表示该索引使用余弦距离操作类。

查询时可以调节搜索范围:

SET LOCAL hnsw.ef_search = 100;

SELECT id, content
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> '[1,0,0]'::vector
LIMIT 10;

SET LOCAL 只在当前事务中生效,适合为某类查询临时增加搜索范围。搜索范围越大,通常越可能找回真实近邻,但延迟和 CPU 消耗也可能增加。它不能改变 Embedding 模型,也不能把错误的距离度量变正确。

不同 pgvector 版本对过滤后的迭代扫描、索引扫描策略等能力可能不同,应以所部署版本的官方文档为准,并通过执行计划和实际数据验证。


六、更新、删除与一致性

一致性回答的是:

一个写操作完成后,另一个读操作在什么时间、什么条件下必须或可能看到它?

它与召回率不同。

  • 召回率低:数据可能已经可见,但近似索引没有找到真实近邻;
  • 一致性较弱:数据可能根本还没有进入本次查询可见的范围;
  • 过滤错误:可能返回了不应访问的数据;
  • 模型错误:数据可见且检索正确,但语义空间表达不对。

1. PostgreSQL + pgvector 的事务边界

pgvector 是 PostgreSQL 的扩展,表数据、向量列、普通索引和向量索引都处于 PostgreSQL 的事务体系中。常见流程如下:

BEGIN;

INSERT INTO documents
    (tenant_id, language, content, embedding, model_name)
VALUES
    (42, 'zh', '新文档', '[0.99,0.01,0]'::vector, 'demo-v1');

-- 在当前事务中执行查询
SELECT id, content
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> '[1,0,0]'::vector
LIMIT 1;

COMMIT;

在同一事务中,查询可以看到自己的写入;事务提交后,其他事务能否看到数据取决于 PostgreSQL 的隔离级别和快照建立时间。普通 PostgreSQL 事务不会因为向量列而失去 ACID 语义。

但需要区分数据库事务与应用层异步流程:

写入原文数据库
  ↓
异步生成 Embedding
  ↓
异步写入向量表

如果原文事务已经提交,而 Embedding 任务尚未完成,按“原文已存在”查询时,向量库中可能暂时没有对应记录。这不是 PostgreSQL 事务失效,而是两个独立事务之间存在处理延迟。

2. Upsert 不是所有系统中的同一种语义

应用常把“插入或更新”称为 upsert,但必须确认:

  • 主键冲突时是覆盖、合并还是报错;
  • 更新向量后旧向量是否立即从检索路径消失;
  • 删除是否立即生效;
  • 更新元数据和更新向量是否原子;
  • 重试请求是否幂等。

例如文档重新分块后,旧的 chunk_id 如果没有删除,可能同时检索到旧分块和新分块。解决方法不是只调用一次 upsert,而是定义明确的版本状态:

document_id = 100
version = 3
status = active

查询只允许 status = active 且版本符合要求的记录,旧版本在确认新版本完整写入后再删除或标记失效。

3. Milvus 中的一致性级别

Milvus 是独立的分布式向量数据库,其写入、消息流、查询节点和持久化组件之间存在异步传播过程。Milvus 文档公开描述了多种一致性级别,常见包括:

  • Strong:读取尽可能等待最新可见数据;
  • Session:同一会话中通常保证能看到自己的写入;
  • Bounded:允许一个有界的新鲜度窗口;
  • Eventually:最终可见,不保证立即读到最新写入。

具体 API、默认值和可用配置应以部署版本和所用 SDK 文档为准。不能仅因为 insertupsert 请求返回成功,就推断下一条查询必然立即看到新数据。

一个典型的可见性路径是:

客户端写入
  ↓
代理接收请求
  ↓
消息写入与分发
  ↓
查询节点获得对应时间戳的数据
  ↓
查询请求选择可见时间点

如果查询使用较弱的一致性级别,可能出现:

写入请求成功
查询暂时查不到新向量
稍后查询才能查到

这在异步索引构建、流式导入、批量导入和副本追赶时尤其常见。

4. 删除的延迟与旧结果

分布式向量系统通常通过删除标记、时间戳和后台压缩处理删除。删除请求被接受后,可能经历:

删除请求成功
  ↓
删除消息传播
  ↓
查询节点应用删除标记
  ↓
旧数据在查询中不可见
  ↓
后台 compaction 回收物理空间

“查询不可见”和“物理空间已经回收”是两个不同时间点。前者关系到业务正确性,后者关系到存储回收和性能。

如果系统返回了刚刚删除的对象,应先检查:

  1. 查询使用的一致性级别;
  2. 删除请求是否真正成功;
  3. 查询是否命中了旧副本或旧时间戳;
  4. 是否存在同一业务对象的旧版本;
  5. 是否发生了客户端缓存。

七、并发写入中的典型竞态

1. 新旧 Embedding 乱序到达

假设同一文档快速更新两次:

版本 2:先提交原文,Embedding 任务耗时较长
版本 3:后提交原文,Embedding 任务先完成

如果两个异步任务都直接写入同一主键,版本 2 可能最后到达并覆盖版本 3,产生“旧内容覆盖新内容”的结果。

解决方式是把版本写入请求中,并在数据库侧约束更新顺序。例如 PostgreSQL 中可使用带条件的更新:

UPDATE document_vectors
SET
    content = $1,
    embedding = $2,
    version = $3
WHERE document_id = $4
  AND version < $3;

如果受影响行数为零,说明该版本已经过期或存在并发冲突,应用不能把它当作成功覆盖。

2. 原文已删除,向量任务后来完成

另一条竞态路径是:

删除原文
  ↓
旧的 Embedding 任务仍在队列中
  ↓
任务完成并重新写入向量

最终系统可能出现“原文不存在,但向量还在”的幽灵记录。常见解决办法包括:

  • 删除时写入 tombstone 或删除版本;
  • Embedding 任务写入前检查对象当前版本;
  • 使用幂等任务键;
  • 查询时额外过滤对象状态;
  • 定期对账原文表和向量表。

3. 多租户检索中的安全边界

错误的实现可能是:

先在全库取 Top-10
再在应用代码中过滤 tenant_id

当租户数据稀疏时,结果可能不足;更严重的是,如果日志、重排器或缓存先接触了全库结果,租户隔离边界已经被破坏。

正确的抽象应当是:

tenant_id 是检索候选集合定义的一部分

过滤应尽可能在数据库查询语义中表达,而不是依赖应用层事后删除结果。


八、诊断:结果不对时先区分哪一层出错

1. 先执行精确基线

对同一批查询,暂时关闭或绕过 ANN 索引,执行精确搜索,并记录:

查询向量版本
距离度量
过滤条件
精确 Top-k
ANN Top-k
Recall@k

如果精确结果就不符合人工判断,应检查:

  • 查询和文档是否使用同一个 Embedding 模型;
  • 文本是否被错误截断;
  • 分块是否过大或过小;
  • 查询是否使用了正确的指令模板;
  • 向量维度和归一化方式是否一致;
  • 距离度量是否与模型训练目标一致。

如果精确结果合理,而 ANN 结果缺失,则再检查:

  • HNSW 的搜索参数;
  • IVF 搜索的分区数量;
  • PQ 压缩误差;
  • 索引是否覆盖最新数据;
  • 过滤是否在 ANN 阶段生效;
  • 查询是否遇到了数据分布变化。

2. 检查距离数值和排序方向

常见错误包括:

把余弦相似度按升序排序
把余弦距离按降序排序
把 pgvector 的负内积当成正内积
把不同距离的数值阈值直接比较

例如 pgvector 中:

ORDER BY embedding <=> $1

是取余弦距离最小的结果;而 <#> 是取负内积最小的结果,也就是实际内积最大的结果。

3. 检查过滤后的候选数量

如果不加过滤能返回 10 条,加上租户或时间条件后只能返回 1 条,应区分:

  • 过滤集合本身是否少于 10 条;
  • ANN 是否只生成了很小的候选集;
  • 系统是否采用后过滤;
  • 是否启用了过滤感知或迭代候选扩展;
  • 过滤条件是否使用了错误字段或错误类型。

不能简单地把 LIMIT 10 改成 LIMIT 100,然后认为结果已经正确。扩大候选数只是补救策略,只有在实际验证中确认候选覆盖率足够时才成立。

4. 检查可见时间而非只看写入返回值

新写入或删除结果异常时,应记录:

写入请求 ID
主键
版本号
提交时间
查询时间
一致性级别
客户端连接或会话标识

Milvus 等分布式系统还应检查查询所使用的时间戳和副本状态;PostgreSQL 则应检查事务是否提交、查询是否处于旧快照以及应用是否读取了缓存。


九、几个容易混淆的结论

“向量越近,语义一定越相似”

不成立。距离只是在指定 Embedding 空间中的几何关系。模型没有学到的语义,数据库无法凭距离补出来。

“余弦距离和 L2 距离总是等价”

不成立。只有向量都归一化时,二者才产生相同排序。

“提高召回率能解决过滤结果错误”

不一定。过滤发生在 ANN 之后时,即使提高搜索范围,也可能仍然无法得到过滤集合内的真实 Top-kk。应先确认过滤语义和执行阶段。

“写入成功后立即查询一定能看到”

取决于系统和一致性配置。PostgreSQL 的事务提交、Milvus 的一致性级别、查询副本追赶以及应用缓存都可能影响可见性。

“召回率高就代表回答质量高”

不成立。RAG 中还要考虑:

  • 文本分块是否完整;
  • 召回内容是否包含正确证据;
  • 过滤是否满足权限;
  • 重排模型是否正确;
  • 最终生成是否引用了实际证据。

向量数据库负责在定义好的向量空间和候选约束下检索,不负责保证整个问答链路的事实正确性。


向量检索的基础可以归纳为一个严格的顺序:

确定对象和分块方式
  ↓
使用一致的 Embedding 模型
  ↓
选择与模型目标匹配的距离度量
  ↓
先定义过滤后的候选集合
  ↓
用精确搜索建立基线
  ↓
用 ANN 在延迟、内存和召回率之间取舍
  ↓
明确写入、更新、删除的可见性和版本规则

只要这条链路中任意一层没有明确,最终的“相似搜索结果”就很难解释,也很难稳定运维。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。