真实业务里,向量检索几乎从不单独存在。用户搜索”2024
年发布的 Python 相关帖子”,系统要同时满足
year = 2024 AND tag LIKE 'python%' 的标量条件和
embedding
的语义近邻。给搜索加过滤条件,看起来只是在
SELECT 里多写几个 WHERE,但在 ANN
索引下会把整套性能假设推翻:HNSW
的图游走不知道哪些邻居被过滤掉了,IVF-PQ 的 posting list
可能 99% 条目不满足条件,DiskANN 的 beam search 一个 I/O
读上来 128 个邻居,只有 2 个能用。
本文处理的问题是:当查询同时包含向量相似和标量谓词(Scalar Predicate)时,如何在保证召回率的前提下让查询仍然快。上半部分讨论理论:pre-filter 和 post-filter 为什么都各有”陷阱”,ACORN(Patel et al., SIGMOD 2024)提出的 predicate-agnostic 索引思路,Milvus / Qdrant 的分区与 pinned filter 策略,以及选择度(Selectivity)对策略的决定性影响。下半部分落到 pgvector 的 SQL 写法、EXPLAIN 观察、与 Learned Query Optimizer 文章里代价模型的联系。
本系列的上一篇是 向量索引深度:HNSW、DiskANN、SPANN 原理对比 ,假设读者已经熟悉 HNSW / IVF / DiskANN 的基本工作方式。
一、问题定义与基本陷阱
1.1 混合过滤查询的形式
一条典型混合查询:
SELECT id, content
FROM docs
WHERE year = 2024 AND tag LIKE 'python%'
ORDER BY embedding <=> query_vec -- 向量距离
LIMIT 10;需求:从满足
year = 2024 AND tag LIKE 'python%' 的文档子集
\(S \subseteq D\) 中,找出
embedding 与 query_vec 最近的 10
条。形式化一点:
Topk_filter(q, k, φ) = argmin_{x ∈ X, φ(x) = true} dist(q, x.vec)
\(\varphi\) 是标量谓词(predicate),可以任意复杂:范围、等值、模糊匹配、布尔组合、地理半径等。
选择度(Selectivity)\(s = |\{x : \varphi(x)\}| / |X|\) 是后续一切策略的关键变量。它的分布通常极度不均:系统里常见 \(s \in [10^{-6}, 1]\) 跨七个数量级。
1.2 三种朴素策略
策略 A:Post-filter(先近邻,再过滤)
拿 ANN 索引查 Top-k'(k' > k),再对结果做 φ 过滤,取前 k 个。
- 优点:不需要改 ANN 索引。
- 致命问题:当 \(s\) 很小(例如 \(s = 0.001\))时,\(k' = k\) 的结果几乎全部被过滤掉。为保证返回 \(k\) 条,要把 \(k'\) 放大到 \(k/s\)——在 \(s = 10^{-3}\) 时要扫 10000 个候选才能拿到 10 条满足条件的。延迟和召回都崩。
策略 B:Pre-filter(先过滤,再暴力或索引)
对满足 φ 的子集 S 做精确 Top-k。小 S 可以暴力扫描;大 S 需要在 S 上建临时索引。
- 优点:召回精确。
- 问题:当 \(s\) 较大(例如 \(s = 0.5\),\(|X| = 10^8\))时,\(|S|\) 仍是 5 千万,暴力扫描不可行;临时建 HNSW 成本高。
- 另一个问题:标量索引(B-Tree、倒排)和向量索引是两套数据结构,pre-filter 得到的行集合不能直接喂给 HNSW。
策略 C:In-filter / Predicate-aware(过滤嵌入检索过程)
把 φ 的判断嵌入到 HNSW 的图游走 / IVF 的 posting list 扫描里,走到一个节点就先判断 φ。
- 优点:在 \(s \in [0.01, 0.5]\) 的”中等选择度”区间里通常是最优的。
- 问题:HNSW 的导航性质(navigability)依赖全图连通,只遍历满足 \(\varphi\) 的节点会让图支离破碎——整片区域可能没有任何节点通过 \(\varphi\),查询陷入局部最优,召回骤降。
这三种策略没有一种普遍最优,选择依赖于 \(s\)、\(|X|\)、索引类型和查询延迟预算。
1.3 选择度区间
工程里一个大致的经验分段(基于 ann-benchmarks 与多个产业报告的综合):
| 选择度 \(s\) | 主流策略 | 理由 |
|---|---|---|
| \(s < 10^{-4}\) | Pre-filter + 暴力扫描 | 候选集小到可以直接算 |
| \(10^{-4} \le s < 10^{-2}\) | Pre-filter + 临时 ANN / 分区索引 | 规模中等,需要索引加速 |
| \(10^{-2} \le s < 0.3\) | In-filter(图游走内嵌过滤) | ACORN、Milvus bitset 过滤的主场 |
| \(s \ge 0.3\) | Post-filter(宽松扩展 \(k'\)) | 大部分候选都满足,扩展 \(k'\) 代价低 |
这些边界不是硬的,和数据分布、向量维度、索引参数都相关,但可以作为策略选择的起点。
1.4 混合检索在系统栈中的位置
把混合过滤检索放回数据库系统栈里看,它的位置处于三个传统模块的交叉点:
- 查询优化器:决定谓词顺序、选择哪个索引、代价估计。
- 向量索引:图或倒排加稠密距离计算。
- 标量索引:B-Tree / Bitmap / 倒排。
传统数据库里这三个模块已经打磨了几十年,但把它们”组合起来对付向量谓词”是近两三年才被系统性研究的问题。ACORN、Filtered-DiskANN、VBase 这些论文的共同贡献在于:把”向量索引”从独立孤岛升级为能和查询优化器对话的一等公民。
二、ACORN:Predicate-Agnostic 图索引
Patel 等人在 SIGMOD 2024 发表的 ACORN(Performant and Predicate-Agnostic Search Over Vector Embeddings and Structured Data)是目前在 in-filter 方向上理论与实现都较完整的工作之一。
2.1 核心观察
ACORN 指出过往混合检索方案的一个普遍缺陷:为特定 \(\varphi\) 预构建索引(例如按 tag 分桶、按 year 分区)不能覆盖任意谓词,而在查询时临时过滤 HNSW 又会把图打断。它提出一种 predicate-agnostic(对谓词不可知)的索引:构建时不依赖任何谓词,查询时能对任意 \(\varphi\) 都保持图连通性。
2.2 技术手段
ACORN 的核心改动有两处:
A. 稠密化底层图。HNSW 第 0 层每个节点度数是 \(M_0 = 2M\);ACORN 把底层度数拉到 \(\gamma \cdot M_0\)(\(\gamma\) 叫 density factor,典型 \(\gamma = 10\)–\(40\))。稠密图的意义是:即使对查询施加强过滤,每个节点仍然有足够多邻居满足 \(\varphi\),贪心游走不会陷入”全部邻居被过滤”的死角。
B. 两跳剪枝(two-hop pruning)。查询时如果某邻居不满足 \(\varphi\),不是直接丢弃,而是继续向它的邻居(即原节点的二跳邻居)扩展,仅保留满足 \(\varphi\) 的点作为候选。这样 HNSW 的”可导航小世界”性质在过滤后依然近似保持。
两跳扩展相当于在运行时构造一个诱导子图(induced subgraph) \(G_\varphi\),而稠密化保证 \(G_\varphi\) 仍连通。代价是:
- 构建时图更大,内存占用 \(\gamma\) 倍;
- 查询时要做更多距离计算(被两跳扩展进来的候选)。
但相比每个谓词都维护专用索引,这套方案只需一份图就能处理任意 \(\varphi\)。
2.3 ACORN 的两个变体
- ACORN-\(\gamma\):静态稠密化,全图边数恒为 \(\gamma M_0\) 倍。
- ACORN-1:只做两跳剪枝,不稠密化。适合内存紧张但谓词选择度不极端的场景。
论文里报告在选择度 \(s \in [0.001, 0.1]\) 区间内,ACORN 相对 pre-filter + FAISS 基线有 2–1000× 的延迟加速,且能保持 95%+ Recall。
2.4 局限
- 极小 \(s\)(\(<10^{-4}\))下 ACORN 仍不如 pre-filter + 暴力;
- 稠密化让写入与内存成本上升;
- 对”每一跳都要查询外部 B-Tree”才能算的复杂谓词(如跨表 JOIN 后的条件),ACORN 本身不处理,还是要依赖上层查询执行器做拉起。
三、Milvus、Qdrant 的工程策略
3.1 Milvus:Bitset + 分区 + 分段
Milvus 从 2.0 起把集合(Collection)切成多个 Segment,每个 Segment 独立建 HNSW / DiskANN / IVF_FLAT 索引。混合过滤查询的流程:
- 标量过滤:对每个 Segment,用标量索引(Bloom、位图、倒排)得到满足 \(\varphi\) 的行 ID 位集合 \(B\)。
- 向量搜索:调用底层索引的
search_with_bitset(q, k, B),在图游走 / posting list 扫描中检查每个候选行的 bit 是否在 \(B\) 中。不在的直接跳过。 - 合并:各 Segment 的 Top-k 合并为全局 Top-k。
这是典型的 in-filter 策略。关键工程点:
- Bitset 的表示(dense vs sparse)随选择度自适应;
- 如果 \(|B|\) 非常小(例如 < 1000),直接 pre-filter + 暴力;
- 如果 \(|B| = |S|\) 几乎全选,直接不做过滤走原始搜索。
Milvus 里称这种在选择度上做策略切换的机制为 “filter plan selection”。阈值是可配置的,典型切点在选择度 0.1% 和 50%。
此外,Milvus 支持分区(Partition):按业务字段(如 tenant_id、year)切 Collection,每个分区独立建索引。查询只在匹配分区上执行。分区可以看作”粗粒度 pre-filter”,适合谓词分布高度倾斜的业务。
3.2 Qdrant:Payload Index + Pinned Filter
Qdrant 的设计思路类似但更细。它的 payload(Qdrant 对标量字段的称呼)可以独立建索引:整数用 B-Tree、字符串用 keyword index、地理字段用 geo index。查询时:
- 先对 payload 索引求交集得到 \(B\);
- 若 \(|B|\) 占比 <
full_scan_threshold(默认 10%),做 pre-filter + 暴力; - 否则走 HNSW + “bitset 过滤”,即 in-filter。
Qdrant 文档里把这个阈值切换叫做 “filterable HNSW” 或 “pinned filter”。0.9 版本之后支持 Rust 层级的 SIMD 距离和 bitset 的位并行,filterable HNSW 在中等选择度下非常快。
一个具体的 Qdrant 查询例子:
from qdrant_client import QdrantClient
from qdrant_client.models import Filter, FieldCondition, MatchValue
client = QdrantClient(url="http://localhost:6333")
res = client.search(
collection_name="docs",
query_vector=q.tolist(),
query_filter=Filter(must=[
FieldCondition(key="year", match=MatchValue(value=2024)),
FieldCondition(key="tag", match=MatchValue(value="python")),
]),
limit=10,
)Qdrant 会自动走上述 in-filter
路径,不需要显式告诉它用哪种策略。但它提供
params.exact = true
强制走暴力,方便做对照实验。
3.3 分区 vs in-filter:选择准则
| 场景 | 首选策略 |
|---|---|
谓词取值空间小且数据分布集中(如 tenant_id
只有 100 个) |
物理分区 |
谓词是范围类、组合多变(如
year BETWEEN 2020 AND 2023 AND tag IN (...)) |
通用 in-filter |
| 谓词包含跨表 JOIN 后的条件 | 先物化为派生列再走 in-filter |
| 选择度 \(<0.1\%\) 的超窄谓词 | pre-filter + 暴力 |
四、Filtered-DiskANN:磁盘图索引上的过滤
HNSW 的 in-filter 思路也被移植到了 DiskANN。Gollapudi 等人 2023 年发表的 Filtered-DiskANN(WWW 2023)给出了两种具体方案:
A. FilteredVamana。在构建 Vamana
图时,每个节点 \(x\) 带一个
label 集合 \(L(x)\)(属于哪些谓词分组)。RobustPrune
被改写为:优先选择与 \(x\)
有 label
交集的候选,保证对任意单一谓词,图上仍然有足够的连通性。代价是图略大、构建时间增加。
B. StitchedVamana。为每个(预定义的)谓词分组独立构建 Vamana 子图,再把子图”缝”在一起:跨子图的连接只保留近邻。查询时用谓词索引定位到相应子图入口,在子图内做 beam search。
StitchedVamana
在谓词分组集合已知时性能优越;FilteredVamana
对新谓词更灵活。生产上 Microsoft 的 DiskANN 实现提供
BuildFilteredIndex,可以通过 API 指定 label
列。
这套方案与 ACORN 的 predicate-agnostic 哲学有区别:Filtered-DiskANN 依然假设”常见谓词集合可预知”,ACORN 不做此假设。选择取决于业务对谓词可枚举性的判断。
五、VBase:放宽单调性假设的查询执行框架
字节跳动与 CMU 合作的 VBase(OSDI 2023)从另一个角度切入混合过滤:它把”向量相似 + 标量过滤”当作一个联合查询优化问题来看待。
传统查询优化器在处理 “ORDER BY similarity LIMIT k WHERE
predicate” 时,需要的假设是:similarity
的输出单调递减,枚举若干候选后可以尽早停止(Top-k 流水线中的
“Aggressive Top-k”)。但 HNSW
的图游走不保证严格单调——邻居扩展可能在中途产生更差的结果再回升。VBase
提出 Relaxed
Monotonicity:允许候选序列在有限步内不单调,只要从全局看仍然收敛。
基于这个新假设,VBase 把 HNSW / IVF 等 ANN 操作作为关系算子,和 Filter、Join 等一起做基于代价的规划。对于一个 “近邻 + 过滤 + JOIN 另一张表” 的查询,VBase 能自动决定:
- 是否提前用谓词索引生成 bitset;
- 是否用 ANN 近邻作为 Semi-Join 的构建方;
- Top-k 停止条件如何根据谓词选择度调整。
这条路线的意义在于:它把向量检索真正纳入关系数据库的查询优化体系,而不是把向量索引当成一个”黑盒 UDF”。pgvector 的 iterative scan、Milvus 的 cost-based planner 都在向这个方向演进,但 VBase 是目前学术上最完整的尝试。
六、pgvector:在关系数据库里做混合检索
pgvector 把向量搜索放回 PostgreSQL,直接和 SQL 的任意 WHERE 共存。理论上这是最自然的混合过滤形式;实际上它也暴露了关系查询优化器与 ANN 索引交互时的不少坑。
4.1 基础写法
CREATE EXTENSION vector;
CREATE TABLE docs (
id bigserial PRIMARY KEY,
year int,
tag text,
content text,
embedding vector(768)
);
-- HNSW 索引(pgvector 0.5.0+ 支持 HNSW)
CREATE INDEX docs_emb_hnsw
ON docs USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
-- 辅助标量索引
CREATE INDEX docs_year_idx ON docs (year);
CREATE INDEX docs_tag_idx ON docs (tag text_pattern_ops);典型查询:
SET hnsw.ef_search = 64;
SELECT id, content, embedding <=> $1 AS dist
FROM docs
WHERE year = 2024 AND tag LIKE 'python%'
ORDER BY embedding <=> $1
LIMIT 10;<=> 是 pgvector
的余弦距离算子(<-> 是
L2,<#> 是负内积)。
4.2 EXPLAIN 观察执行计划
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, embedding <=> $1 AS dist
FROM docs
WHERE year = 2024
ORDER BY embedding <=> $1
LIMIT 10;在选择度不同的数据上会看到两种截然不同的计划:
计划 A:Index Scan on HNSW(post-filter)
Limit
-> Index Scan using docs_emb_hnsw on docs
Order By: (embedding <=> $1)
Filter: (year = 2024)
Rows Removed by Filter: 450
pgvector 走 HNSW 索引排序,逐条检查
year = 2024,丢弃不匹配的。当选择度高时有效,选择度低时
Rows Removed by Filter
会爆炸增长,且可能返回不足 10 条——pgvector
只会扫描 ef_search
个候选,若过滤剩余不足就不足。这是 pgvector
的一个著名坑。
计划 B:Bitmap Scan(pre-filter)
Limit
-> Sort
Sort Key: (embedding <=> $1)
-> Bitmap Heap Scan on docs
Recheck Cond: (year = 2024)
-> Bitmap Index Scan on docs_year_idx
选择度低到某个阈值时,优化器认为 B-Tree 扫描 + 排序比 HNSW 更便宜,就走标量索引 + 暴力排序。
4.3 Iterative Index Scan
pgvector 0.8(2024 年末)引入了 iterative index
scan(hnsw.iterative_scan = strict_order
或 relaxed_order),专门修这个问题。启用后当
HNSW 前 ef_search
个候选过滤不够时,会继续扩展,直到返回满足 LIMIT
的结果或达到 hnsw.max_scan_tuples
上限。代价是高选择度下的延迟会被不必要地拉长,hnsw.max_scan_tuples
必须显式设置。
SET hnsw.iterative_scan = strict_order;
SET hnsw.max_scan_tuples = 20000;这个功能本质上是 pgvector 版本的 in-filter。相比 Milvus/Qdrant 的 bitset 方案,pgvector 依赖 PG 的表达式执行来判谓词,代价更高,但能直接处理任意 SQL 谓词。
4.4 IVFFlat 与 HNSW 的选择
pgvector 也支持 IVFFlat:
CREATE INDEX docs_emb_ivf
ON docs USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 1000);
SET ivfflat.probes = 10;IVFFlat 构建快、内存小,但召回率不如 HNSW。混合过滤时 IVFFlat 的表现较差:每个 list 内是顺序扫描,过滤后召回进一步下降。大多数场景下 pgvector 的建议是默认 HNSW。
4.5 与学习型查询优化器的关系
传统 PG 查询优化器不知道 HNSW 的代价模型——它只有
hnsw.ef_search 这个黑盒、不知道选择度对 HNSW
效率的非线性影响、也不了解迭代扫描的成本上界。混合过滤场景下优化器经常做出次优选择(例如该走
bitmap scan 时仍固执地走 HNSW)。
这是 learned query optimizer 的天然用武之地:给代价模型喂选择度、向量数据分布、索引参数,直接学习”选 Bitmap 还是 HNSW”的决策。目前这条路在学术上有 VBase、Milvus 的 cost-based planner 等尝试,工业级部署还不成熟。
6.5 pgvector 0.7+ 的新特性
2024 年底发布的 pgvector 0.7 引入了几个对混合检索意义重大的特性:
- 二分量化(Binary Quantization):把每维压到 1 bit,索引体积缩小 32×,适合百亿规模在 PG 上粗排。
- Half-Precision(halfvec):fp16 存储,内存减半,召回几乎不受影响。
- 稀疏向量(sparsevec):支持 BM25 / SPLADE 风格的稀疏表示,为 hybrid search 提供了原生类型。
- Iterative Index Scan 的 GUC
细调:
hnsw.iterative_scan可以从off / relaxed_order / strict_order三档切换,生产里通常用strict_order保证顺序稳定。
这些更新让 pgvector 逐渐从”够用的扩展”变成”能打的生产选项”。对有 PG 运维团队的组织来说,0.7+ 后的 pgvector 值得重新评估。
6.6 与关系查询的联合优化
pgvector 最大的长处在于它能和
JOIN、GROUP BY、WINDOW
自由组合。一个典型生产查询:
WITH candidates AS (
SELECT id, embedding <=> $1 AS dist
FROM articles
WHERE tenant_id = $2 AND published_at > now() - interval '30 days'
ORDER BY embedding <=> $1
LIMIT 50
)
SELECT a.id, a.title, s.author, count(c.id) AS comment_cnt
FROM candidates a
JOIN article_stats s USING (id)
LEFT JOIN comments c ON c.article_id = a.id
GROUP BY a.id, a.title, s.author
ORDER BY count(c.id) DESC
LIMIT 10;七、工程落地的几个陷阱
7.0 选择度估计不是常数
一个被忽视的事实:同一条 SQL
的选择度会随时间变化。status = 'active'
今天可能选择度 0.3,半年后可能降到 0.05(因为业务增长让非
active 记录占比上升)。如果系统用的是静态阈值(“\(s < 0.1\) 走
pre-filter”),会在业务变化时悄悄劣化。
生产系统里最好的做法:
- 定期重新采样选择度分布(每日或每周)。
- 给查询打标签,按标签分桶监控性能;发现某桶劣化时报警。
- 在代价模型里留足容错区间,不要让策略切换发生在选择度的边界区。
- 把选择度估计作为索引重建的触发条件之一。如果某类谓词的真实选择度和代价模型假设偏差超过一定阈值,触发重建或参数重新调优。这种”自适应”特性目前还很少有系统原生支持,大多依赖应用层自己实现监控闭环。
7.1 索引维护与选择度漂移
除了选择度本身的漂移,索引质量也会随着写入增长而退化:新数据分布和老数据不一致时,过滤命中率会下降,间接放大混合检索的代价。两个经验法则:
- 增量写入量达到原数据 20%–30% 时重建或 rebalance 一次;
- 对带过滤的 HNSW,监控”命中后仍需扫描的邻居数”——这是图连通性退化的直接指标。
5.1 ef_search 的”过滤穿透”
这是 pgvector / Milvus HNSW 用户最常踩的坑。用户设
LIMIT 10, ef_search = 40,认为拿到 40
个候选过滤到 10 个绰绰有余,结果选择度 0.01
时实际满足条件的候选不到 1 个,返回 0 行。
规避:
- pgvector 开启
iterative_scan = strict_order+ 合理的max_scan_tuples; - Milvus 根据选择度动态调
ef_search = max(k / s, base_ef); - 在应用层做兜底:Top-k 不足时放大 ef_search 重试。
5.2 过滤谓词对图结构的破坏性
即便 ACORN 的稠密化,也只是让”大多数谓词”下图连通。极端谓词(比如选出某个远离训练分布的子簇)仍可能让查询陷在一个断开的子图里,召回崩盘。
规避:
- 对业务上已知的大类 \(\varphi\)(tenant_id、语言、类别)走物理分区;
- 对动态谓词做离线评估:挑一批代表性 \(\varphi\) 跑 Recall 对照,发现问题的谓词加入专用索引白名单。
5.3 多向量字段 + 多谓词
一条文档可能有 title_vec 和
body_vec
两个向量字段,查询要做加权融合。这个问题当前所有系统都不在
ANN 索引内直接解决——要么在应用层做两次检索再 RRF(Reciprocal
Rank
Fusion)合并,要么把两个向量拼接成一个。工程上建议不要依赖单一系统的”多向量原生”特性,手工合并更可控。
5.4 基准测试的正确方法
做混合过滤检索的基准测试必须同时报告:
- 数据集规模 \(|X|\);
- 谓词选择度 \(s\)(若有多档,给多点);
- 召回率目标(Recall@k = 0.9 / 0.95 / 0.99);
- 查询端到端延迟(P50、P95、P99);
- 指定达到召回目标所需的调参(ef_search 等)。
只报告一个”QPS 3000”没有意义。ann-benchmarks 项目 2024
年加入了 filtered-search
轨道,可以作为参考方法论。
八、一条决策路径
整理一条可执行的选型/调参流程:
- 量化选择度分布。采样一批真实查询,统计 \(s\) 的分布(中位数、尾部)。
- 定义召回目标。业务能接受 0.9 还是必须 0.99?
- 选择架构:
- 若谓词主要是少数枚举字段 → 物理分区(Milvus/Qdrant partition)。
- 若谓词复杂、选择度中高 → 带 bitset / iterative scan 的 HNSW。
- 若选择度极低且谓词有选择性索引 → pre-filter + 暴力。
- 若要同时支持任意谓词并维持高召回 → 调研 ACORN 或等价的稠密化 HNSW。
- 离线压测:对选择度分位点各做一次召回 + 延迟测试。
- 线上监控:记录”实际扫描候选数 / LIMIT”比值,突增说明选择度漂移,要触发重新调参。
关键的一条:不要相信任何”一套参数打天下”。在生产系统里,选择度分布会随业务演进,需要周期性重新测试。
九、三个业务场景的参考方案
9.1 RAG 检索 + 租户隔离
多租户 SaaS 场景下 tenant_id
是几乎所有查询的必带谓词。选择度通常很小(\(s =
1/N_{\text{tenants}}\))。
推荐方案:按 tenant_id
做物理分区(Milvus/Qdrant)或独立
collection。跨租户查询几乎不存在,物理隔离既能显著减少向量检索量,又能满足合规要求(租户数据物理分离)。千级租户可以一个分区一个;万级租户要做分区合并或层级分区。
9.2 内容推荐 + 实时特征过滤
推荐系统里的向量召回经常带有
country = 'CN' AND category = 'electronics' AND price < 1000 AND in_stock = true
这种多条件组合。每个条件单独的选择度可能都不小,但组合后很难预测。
推荐方案:
- 对低基数、高频字段(country、category)用分区或带标签的 DiskANN;
- 对数值范围(price)用 in-filter;
- 对动态字段(in_stock)在应用层做 post-filter 兜底;
- 设置合理的 \(efSearch\) 下限 + iterative scan 上限。
把”结构化的组合”拆成”物理 + 标签 + 运行时”三档,是在这种场景下控制代价最稳的思路。
9.3 全文 + 向量的混合搜索
在搜索产品里,关键词匹配(BM25 / Elasticsearch)与向量检索经常同时使用,通过 Reciprocal Rank Fusion(RRF)融合。这里的”混合”不是单次查询的 filter,而是两路独立召回再合并。
推荐方案:
- 不要试图把 BM25 的倒排塞进 HNSW;
- 用 Elasticsearch / OpenSearch 保留关键词路径,Milvus/Qdrant 走向量路径;
- 在应用层做 RRF,或用 OpenSearch 2.x / Elasticsearch 8.x 的 hybrid query 原生融合;
- 过滤谓词同时下推到两路,不要只在其中一路做,否则融合时召回会不对称。
SPLADE、ColBERT 这类”稀疏 + 稠密”模型正在把两路融合。但从工程现实出发,分离召回路径仍然更可控。
十、多表 JOIN 下的向量检索
把向量检索和多表 JOIN 结合起来是另一种更复杂的场景。例:用户表、商品表、交互记录三张表,查询”给定用户 u,从他最近 30 天交互过的商品里找与查询向量最相似的 10 个”。这样的查询需要:
- 在交互表上做时间过滤 + 按用户过滤;
- 得到商品 ID 列表;
- 在商品向量索引上做 Top-k。
现有向量库大多不支持把”外部表的 ID 列表”直接作为向量索引的输入。三种实现思路:
- 物化派生列:把”用户最近 30 天交互的商品”定期物化成用户的一列数组,用数组过滤做谓词。成本是数据冗余和刷新延迟。
- 两阶段查询:先在关系数据库里求 ID
列表,再用
id IN (...)喂给向量库。ID 列表过长(>10000)时性能崩溃。 - 向量库内部 JOIN:只有少数系统(如部分企业 Milvus、ByteHouse)支持原生跨表 JOIN,且对谓词结构有限制。
这个方向目前没有通用的好解。VBase 的框架在论文里涉及了一些,但工业实现仍然初步。生产项目多数情况下只能靠物化和冗余。
十一、混合过滤对监控的含义
上线混合过滤检索后,监控必须变厚。传统纯 ANN 只需监控 QPS、延迟、Recall;混合场景下至少加:
- 平均选择度 per query class:选择度突变是最早的信号。
- 每查询扫描候选数 / LIMIT 比:如果均值持续上升,说明过滤路径失效。
- 返回不足比例:LIMIT=10 实际返回 <10 的比例。这是 ef_search 过滤穿透的直接指标。
- 策略选择分布:如果系统支持多策略切换(如 Qdrant),要记录走 pre-filter / in-filter / post-filter 的比例。
把这些指标接入告警,能提前发现 “数据分布漂移 → 选择度变化 → 策略失配” 的链式故障。
十二、实现一个混合过滤向量搜索的最小骨架
一个教学型最小骨架可以这样组织(伪代码):
def hybrid_search(q, predicate, k, strategy="auto"):
"""Top-k vector search with scalar predicate φ."""
if strategy == "auto":
s = estimate_selectivity(predicate)
if s < 1e-4:
strategy = "prefilter"
elif s < 0.3:
strategy = "infilter"
else:
strategy = "postfilter"
if strategy == "prefilter":
ids = scalar_index.scan(predicate) # B-Tree / inverted
return brute_topk(q, ids, k)
if strategy == "postfilter":
cands = vec_index.topk(q, k_over=k * 8)
return [c for c in cands if predicate(c)][:k]
if strategy == "infilter":
bitset = scalar_index.as_bitset(predicate)
return vec_index.topk_with_bitset(q, bitset, k, ef=max(64, int(k/s*2)))这 20 多行抽象覆盖了本文讨论的所有关键策略。工业系统比这复杂百倍,但所有复杂性都是围绕这三条路径的优化与边界处理。如果你正在自研或评估混合检索系统,这段骨架可以作为心智模型。
十三、延展阅读
与本文主题交叉的研究方向:
- 学习型代价模型:把 ANN 索引的代价函数纳入查询优化器,见 Learned Query Optimizer 。
- 图增强检索(GraphRAG):把”过滤”扩展为”图拓扑”上的约束,见 GraphRAG 。
- 多模态存储:把向量、图、标量合为一等公民的数据模型,见 多模态数据库 。
十四、与 Learned Query Optimizer 的交互
向量查询要进入通用 SQL 查询优化器,必须让优化器理解向量索引的代价。这和系列文章 Learned Query Optimizer 讨论的”学习型代价模型”天然相关:
- 向量索引的代价很难用规则枚举,因为它依赖查询向量的分布(“靠近簇中心”的查询和”孤岛查询”代价完全不同)。
- 学习型模型可以从查询历史中学出
f(q, ef, predicate) → latency的映射,直接指导优化器。 - 目前 PG 生态里 pgvector 对此支持有限,它的 cost 估计是静态公式(基于 tuple 数和维度),无法反映 HNSW 图的形状。
未来一个合理的演进方向是:向量索引在规划期暴露更多可观测量(预估扫描数、预估距离计算次数),优化器据此做联合决策。这方面 VBase 的 relaxed monotonicity 框架提供了形式化语言,但生产实现还在早期。
十五、总结:一张决策矩阵
| 场景 | 选择度 | 查询频率 | 推荐策略 |
|---|---|---|---|
| 租户隔离 | \(s\) 极小、固定 | 高 | 物理分区(Milvus/Qdrant partition) |
| 多字段组合过滤 | 0.01–0.5 | 高 | In-filter + 带 bitset 的 HNSW |
| 低基数枚举 | 小、可枚举 | 高 | Filtered-DiskANN 或标签分区 |
| 极低选择度 | \(< 10^{-4}\) | 低 | Pre-filter + 暴力或小 IVF 扫 |
| 任意谓词、高召回 | 任意 | 中 | ACORN 或等价稠密化 HNSW |
| 范围查询 + 向量 | 数值范围 | 中 | 范围索引 + in-filter |
这张表不是金科玉律,但作为第一版架构草稿足够用。任何一条都要再配合实际选择度分布和延迟预算验证。
混合过滤检索是向量数据库从”玩具”走向”通用检索基础设施”的关键一步。只有当向量检索能和 SQL 谓词、索引优化器、多字段过滤无缝组合时,它才真正成为数据库的一等公民。到 2024 年,这个目标在工程上已经基本实现,但在理论上(代价模型、正确性保证、跨系统一致性)还留有很多值得研究的空间。
参考文献
- Patel, L., Kraft, P., Guestrin, C., Zaharia, M. “ACORN: Performant and Predicate-Agnostic Search Over Vector Embeddings and Structured Data.” SIGMOD, 2024. https://arxiv.org/abs/2403.04871
- Wang, J., Yi, X., Guo, R., et al. “Milvus: A Purpose-Built Vector Data Management System.” SIGMOD, 2021.
- Gollapudi, S., et al. “Filtered-DiskANN: Graph Algorithms for Approximate Nearest Neighbor Search with Filters.” WWW, 2023.
- Zhang, Q., Xu, S., Chen, Q., et al. “VBase: Unifying Online Vector Similarity Search and Relational Queries via Relaxed Monotonicity.” OSDI, 2023.
- Milvus Docs, “Filtered Search and Hybrid Search”, https://milvus.io/docs/hybridsearch.md
- Qdrant Docs, “Filtrable HNSW”, https://qdrant.tech/documentation/concepts/indexing/
- pgvector, “Iterative Index Scans”, https://github.com/pgvector/pgvector#iterative-index-scans
- Aumüller, M., Bernhardsson, E., Faithfull, A. “ANN-Benchmarks: A Benchmarking Tool for Approximate Nearest Neighbor Algorithms.” Information Systems, 87, 101374, 2020.
- Malkov, Yu. A., Yashunin, D. A. “Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs.” IEEE TPAMI, 42(4), 2020.
- PostgreSQL Docs, “Using EXPLAIN”, https://www.postgresql.org/docs/current/using-explain.html
上一篇:向量索引深度:HNSW、DiskANN、SPANN 原理对比 下一篇:GraphRAG:图增强检索的理论与工程
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【数据库研究前沿】向量索引深度:HNSW、DiskANN、SPANN 原理对比
系统拆解 HNSW、DiskANN/Vamana、SPANN 三类主流 ANN 索引的原理、构建算法、查询流程与工程参数,并覆盖 IVF-PQ、ScaNN 的位置,最后给出 FAISS/Milvus/pgvector/Qdrant 的选型与一份 200 行 numpy HNSW 复现。
【数据库研究前沿】数据库作为 LLM 记忆体:语义缓存、RAG 与一致性
把数据库当 LLM 长期记忆的系统视角:GPTCache、MemGPT、向量 vs 事实记忆;用 pgvector + 触发器实现会话级一致性语义缓存
【数据库研究前沿】系列导论:从 System R 到 AI-Native 的 2026 研究地图
以 System R、Postgres、Bigtable、Spanner、Snowflake 等关键节点串起 50 年数据库史,勾勒 2026 年 AI-Native、向量检索、HTAP 云原生、新硬件、隐私计算、新范式、方法论七条主线,并给出 25 篇系列文章的完整阅读地图。
【数据库研究前沿】如何读数据库顶会论文:SIGMOD/VLDB/CIDR 阅读路线
从顶会定位、检索渠道、三遍读法到工业与学术论文的辨别方法,给出 2023–2025 年数据库领域可信必读二十篇,并配套 CMU 15-721、Stanford CS 245 等公开课清单。