15.4 可复现的向量检索
向量列并不自带“语义”。它只是一种有维度的数值,配合一个距离/相似度函数 产生次序。语义来自向量生成过程;查询性能来自精确或近似执行路径;质量来自 外部标注。
把三者分开,才能复现,也才能退出。
15.4.1 维度、距离度量与归一化
一列必须有模型身份
最小表不是:
而是至少:
真实系统还可能需要:
同一维度不代表同一空间。两个 1536 维模型生成的向量不能因为类型相容就放进 同一个近邻索引比较。一次模型升级应视为数据迁移,而不是悄悄覆盖列。
本章用:
四维分别是 audio、kitchen、outdoor、books 的人工主题坐标。它的价值是每个 距离可手算、没有 API 漂移;它不证明真实 embedding 的语义能力。
四个常见距离/相似合同
pgvector 0.8.4 对 vector 提供:
| 运算符 | 含义 | HNSW opclass |
|---|---|---|
<-> |
L2 / Euclidean distance | vector_l2_ops |
<#> |
negative inner product | vector_ip_ops |
<=> |
cosine distance | vector_cosine_ops |
<+> |
L1 / taxicab distance | vector_l1_ops |
还有 binary vector 的 Hamming/Jaccard,以及 halfvec、sparsevec 的相应
能力;本章不展开。
pgvector 让“越小越近”符合 PostgreSQL ascending index scan:
不要把 <#> 的负数直接叫“相似度”。用于展示内积时要乘 -1,用于索引
排序则保留升序负内积。
选距离要回到模型合同
L2:
它同时受方向与向量长度影响。
cosine similarity:
更强调方向,cosine distance 通常为 。
inner product:
同时受方向与模长影响。某些模型明确训练为用 dot product 排序。
若所有向量都被归一化为单位长度:
此时 L2、cosine 与 inner product 的次序存在紧密关系;但仍应按模型文档 和性能目标选 opclass,不能因为数学关系就混用未归一化数据。
模型 ADR 要回答:
一个常见错误:
索引是 cosine,查询却用 L2;二者 operator family 不匹配,不能期待该索引 支持查询。目录与执行计划必须同时复核。
维度是存储和索引合同
本章使用 vector(4),数据库拒绝不同维度的值。pgvector 0.8.4 的 HNSW
vector 索引支持最多 2000 维;halfvec、bit、sparsevec 有各自上限。
这些是当前版本事实,升级前重查官方文档。
“维度越高语义越好”不是规律。维度会影响:
- 每行存储;
- index tuple 与图内存;
- 构建和查询计算;
- WAL、备份和网络;
- 模型能力与压缩损失。
先以模型要求为输入,再用真实规模测成本。不要为了迎合数据库索引上限随意 截断向量;降维、量化或子向量都需要新的质量基线。
15.4.2 精确近邻与近似索引
默认是精确搜索
没有近似索引时:
PostgreSQL 对所有合格行算距离、排序并取前三。这是 exact nearest neighbor: 在当前快照和距离合同下,召回是完备的。它的成本随参与比较的行数、维度与 并行度增长。
本章质量视图不是直接执行一个可能被 HNSW 接管的 ORDER BY ... LIMIT。
它先产生所有 pair 的 distance,再用 window function 排名:
这使质量 golden 明确来自 exact 全集,与线上 planner 是否选择 ANN 无关。
强制 exact plan:
得到:
这是本章的 reference path。
ANN 用召回换速度
pgvector 支持 HNSW 与 IVFFlat:
官方 README 明确指出,增加 approximate index 后,同一查询可能得到不同 结果;必须把这种差异视为算法合同,而不是数据库 bug。
本章建:
默认值在 pgvector 0.8.4 是 m=16、ef_construction=64;本章用较小值
只是让夹具声明显式,并非生产建议。
HNSW 建多层图:
- 通常有较好的 speed/recall trade-off;
- 构建更慢、内存更多;
- 不需要像 IVFFlat 那样先训练 lists,所以空表也能先建;
- 持续写入仍要维护图和 WAL。
IVFFlat 把向量分到 lists,查询探测部分 lists:
- 构建更快、内存较少;
- 通常查询 speed/recall trade-off 低于 HNSW;
- 建索引前应有代表性数据;
- lists/probes 选择直接影响 recall。
这不是永久排名。数据规模、更新率、过滤、内存和 SLO 会改变选择。
索引查询形状必须匹配
让 ANN index 生效,典型查询要:
只写距离范围:
不一定形成同样的 ordered index path。pgvector 官方建议把范围条件与
ORDER BY、LIMIT 结合。
还要避免把 indexed expression 包进不等价表达式:
展示 similarity 可以在外层计算;候选扫描保持与 opclass/operator 一致。
ANN recall 必须相对 exact 定义
对于同一 query 与 filters:
本章 ann-compare.sql 在同一事务中:
- 从 exact 质量视图取 q06 前三;
- 强制 HNSW path 并取同样过滤后的前三;
- 求集合交集。
结果:
一次查询、17 行、四维向量上的 1.0 不是生产结论。正式测量至少按:
报告分布,并把 exact 抽样任务长期保留。pgvector 官方 monitoring 建议同样 是关闭 index scan 取得 exact 结果,再与近似结果比较。
15.4.3 索引参数、过滤条件与召回代价
HNSW 有构建参数和查询参数
构建参数:
| 参数 | 作用 | 增大通常带来的影响 |
|---|---|---|
m |
每层最大连接数 | 图更密、潜在召回更好、空间/构建更贵 |
ef_construction |
构图候选列表大小 | 潜在召回更好、构建/写入更慢 |
查询参数:
| 参数 | 作用 | 增大通常带来的影响 |
|---|---|---|
hnsw.ef_search |
查询动态候选列表 | recall 上升机会、延迟与工作量上升 |
pgvector 0.8.4 默认 ef_search=40。单次实验应使用事务局部设置:
不要把 session pool 中遗留的 GUC 当作服务配置;也不要只测一个值。需要 绘制:
参数发布要与 index build identity、模型、数据快照和查询集一起版本化。
过滤为什么会“吃掉”结果
pgvector 0.8.4 官方说明,approximate index scan 的普通过滤是在扫描后应用。 假设只有 10% 行满足过滤,初次探索 40 个候选,平均可能只留下约 4 个;需要 K=10 时就会短缺。
这不是 SQL 三值逻辑错,而是 ANN 没继续探索足够候选。
可选策略:
- 过滤列 B-tree + exact search 当过滤后集合很小,先用普通索引缩小到少量行,再 exact 排序,常常更快且 recall 完整。
- iterative index scan 过滤后不足时继续探索 ANN。
- partial HNSW index
少数稳定类别各有索引,例如
WHERE category_id=123。 - partitioning 过滤维度离散且能形成真实数据生命周期/裁剪边界时分区。
- 扩大 candidate/ef_search 简单但增加每次查询成本,仍要测。
本章同时创建:
它提醒读者:向量检索仍然是 PostgreSQL 查询,普通关系索引和选择性统计并未 失效。
iterative scan 的 strict 与 relaxed
pgvector 从 0.8.0 起支持 iterative index scan:
strict 保持距离严格次序;relaxed 允许轻微乱序,可能获得更好的 recall。 relaxed 结果若要重新严格排序,官方示例使用 materialized CTE 后在外层排序。
迭代不会无限进行,还受:
等边界影响。返回 K 行、返回顺序与 recall 都要分别断言。
本章用 strict:
只为让证据顺序稳定。它不能让 approximate graph 等价于 exact scan。
partial index 不是“每个租户建一个”
官方建议在过滤值很少时考虑 partial HNSW:
若有成千上万租户或高基数 ACL,给每个值建索引会造成:
- index 数量爆炸;
- DDL/catalog/autovacuum 管理困难;
- 写放大;
- planner 规划开销;
- 备份恢复和升级时间增长。
高基数过滤更可能需要:
- exact on a selective conventional index;
- 合理分区;
- 分片/路由层;
- 两阶段候选与业务过滤;
- 或重新选择搜索架构。
任何方案都必须验证权限过滤不会因“为了 recall”被放宽。
构建与维护的安全线
HNSW 构建图若能放入 maintenance_work_mem 会更快;官方也警告不要把该参数
设到耗尽 server 内存。生产建索引:
可以减少阻塞写入,但会更慢、产生更长资源占用,失败还可能留下 invalid index。要监控:
并检查:
本章 setup 是离线教学重建,所以用普通 CREATE INDEX;不得把这个选择直接
搬到在线主表。
何时退回 exact
以下场景 exact 可能更好:
- 过滤后只剩少量行;
- K 较大,ANN 需要探索大部分集合;
- 质量要求不允许近似漏召回;
- 数据规模尚小;
- 向量更新频繁,ANN 维护成本超过收益;
- exact 可以在可接受延迟内并行完成。
先测 crossover,再引入 ANN。拥有 HNSW 扩展能力不构成使用理由。
15.4.4 冻结文本、模型标识、许可证、向量文件与校验和
“同一个模型”仍可能生成不同向量
可复现输入至少包括:
只记录营销名,例如 embedding-v3,不够。云服务可能滚动更新后端;本地模型
可能因权重、tokenizer、运行库或量化不同而变化。
本章如何消除外部变量
商品与 query 的向量都保存在冻结 CSV 中。setup 载入后,数据库导出:
再与源文件逐字节 cmp。这比“查询结果差不多”严格得多:向量文本一个数字
变化就失败。
文件 hash 与数据库 checksum 各负责什么
fixture manifest 固定:
最终数据库 business_checksum 则覆盖:
二者互补:
- file hash 证明输入资产没变;
- database checksum 证明装载、计算和最终行为没变。
运行时间、OID、绝对路径、索引物理页大小不进入 business checksum,因为它们 不是跨重建稳定的业务事实。
许可证和数据边界不能等上线后再补
向量可能泄露源数据特征,也可能受模型服务条款约束。ADR 至少记录:
“只传向量,不传原文”也不是自动匿名。是否能从向量推断敏感信息要按威胁 模型评估。
本章文本和数字都是项目自有合成 fixture,不调用外部服务:
这使仓库可重复,不替真实项目完成法务评审。
模型升级要双版本迁移
不要:
在同一列原地混写。更稳健的流程:
若维度改变,类型和索引自然需要新对象;即使维度相同,也应保持逻辑隔离。
退出路径
pgvector 列可转文本导出:
但“能导出字符串”只是技术出口第一步。还要证明目标系统:
- 能按同一精度解析;
- 保留 document/model identity;
- 使用同一距离与归一化;
- 重建索引后质量不变;
- 在切换窗口双读校验;
- 能回放迁移期间更新。
本章精确 reset 删除 shop_ch15,却保留第 14 章持有的扩展;它演示了对象
边界,不是生产数据迁移演练。
上一节:模糊匹配与拼写容错 · 返回本章目录 · 下一节:混合检索与排序验证 · 查看全书目录 · 查看索引中心