搜索与检索:从数据库镜像到证据引擎¶
员工在内部知识库里搜"差旅报销上限",那份刚修订完的《费用管理办法》几分钟前就已经在数据库里提交成功了,但搜索结果里死活找不到它——或者更隐蔽的情况:搜到了,但排在第五位,模型生成答案时压根没把它当作证据引用。这类问题的根子不在数据库,而在于数据库里"存在"和搜索里"能被命中"是两件不同的事。
在 RAG 架构中,检索层进一步承担了大模型的证据引擎角色:召回质量直接决定生成内容的事实可靠性;召回失败,模型就只能凭参数记忆瞎编。下面几节会一直用这个"报销政策搜不到/搜不准"的场景,把检索的四种技术形态、索引延迟、混合检索和权限收束串起来讲。
1. 检索维度的四象限模型¶
不同的检索请求在工程本质上存在根本性差异。混用技术栈会导致要么过度工程化,要么召回质量不达标:
| 类型 | 目标 | 核心技术 | 关注点 | 典型失效表现 |
|---|---|---|---|---|
| 查状态 | 确认权威事实 | 数据库 B-Tree / 联合索引 | 事务、准确性 | N/A(确定性查询,比如按报销单号精确查询状态) |
| 找内容 | 关键词/标题匹配 | 倒排索引(ES)+ TF-IDF/BM25 | 分词、相关性评分 | 分词器把"差旅报销"切成"差旅"和"报销"两个词后权重错位,精确术语搜不到 |
| 找相似 | 语义相近/意图理解 | 向量检索(ANN: HNSW, IVF-PQ) | Embedding 质量、距离度量 | 向量模型对"报销上限 5000 元"这类结构化数字特征表征能力弱 |
| 找证据 | 支撑模型回答 | RAG 混合检索 + 稠密 Rerank | 召回率、权限收束 | 召回证据不全或冲突证据(新旧两版政策)未被过滤 |
2. 索引链:从数据写入到检索可见¶
数据库 COMMIT 成功仅表示数据进入持久化层,并不意味着其对检索层可见。写入与"可搜索"之间存在异步索引化的延迟链路——这正是《费用管理办法》新版本提交后几分钟内搜不到的物理原因。
- 异步索引化流水线:文档正文需经过
分词 → 停用词过滤 → 构建 Segment的处理流水线,通常由 MQ 驱动异步完成。索引构建过程中涉及分析器(Analyzer)选择、自定义词典加载和字段映射(Mapping)定义,任何一环配置不当都会导致召回质量下降。 - Refresh 延迟与可见性窗口:在 Lucene/ES 体系中,数据写入内存 Buffer 至变为可查询状态(Refresh)存在秒级延迟(默认
refresh_interval=1s)。当出现"详情页数据可见但搜索不到"的现象时,应优先排查索引刷新频率,而非查询逻辑本身。该参数控制着写入吞吐与检索可见性之间的时延折中——降低refresh_interval可提升实时性,但会增加 I/O 压力和 Segment 碎片化。 - Segment 合并(Merge):大量小批写入产生的碎片化 Segment 会降低查询性能(每次查询需遍历更多 Segment)。后台合并过程是 CPU 和 I/O 资源的消耗大户。合并策略的选择直接影响写入吞吐与查询 P99 之间的平衡。
3. 召回模式:精确匹配与语义匹配的互补¶
- 倒排索引(Keyword 检索):基于分词后的 Term 精确匹配,通过 TF-IDF/BM25 评分排序。针对报销单号、条款编号、错误码等稀疏离散信号具有高精度,是 RAG 系统防止幻读的基础保障。当模型需要引用"《费用管理办法》第 3.2 条"这类精确条款编号时,倒排索引是唯一可靠的召回路径。
- 向量检索(Semantic 检索):将文本通过 Embedding 模型映射为高维向量,基于余弦相似度(Cosine)或欧氏距离(L2)进行近似最近邻(ANN)搜索。员工搜"出差住宿能报多少钱"这类模糊自然语言描述时,向量检索能命中标题完全不含这些词的条款。但向量模型对结构化特征(金额、日期、编号)的表征能力有限,容易产生"语义正确但事实错误"的召回。
- 混合检索(Hybrid Search):工业界标准实践是 RRF(Reciprocal Rank Fusion)——将关键词检索与向量检索的排名进行倒数融合加权:
其中 \(k\) 为平滑常数(通常取 60)。混合检索可有效防止纯向量模型在处理报销金额、条款编号等强特征时的语义漂移失效。一个典型的服务端实现:
def hybrid_search(query: str, tenant_id: str, dept_scope: list[str], top_k: int = 20):
# 两路召回并行发起,权限过滤直接下推到检索请求里
bm25_hits = es_client.search(
index="policy_docs",
query={
"bool": {
"must": {"match": {"content": query}},
"filter": [
{"term": {"tenant_id": tenant_id}},
{"terms": {"dept_scope": dept_scope}},
{"term": {"status": "published"}},
],
}
},
size=top_k,
)
query_vec = embed(query)
vector_hits = vector_db.search(
vector=query_vec,
filter={"tenant_id": tenant_id, "dept_scope": {"$in": dept_scope}},
top_k=top_k,
)
# RRF 融合两路排名,k=60 是业界常用的平滑常数
fused = reciprocal_rank_fusion([bm25_hits, vector_hits], k=60)
return fused[:top_k]
4. 索引层的权限收束与版本治理¶
搜索索引通常包含部分业务元数据(租户标识、权限标签),构成业务真相的"索引投影"。权限控制必须在检索层硬性执行,不可依赖上层业务逻辑的事后过滤——比如《费用管理办法》有一份仅限财务部可见的补充细则,如果权限过滤在应用层做,向量召回阶段就可能把它当作候选证据喂给模型。
- 前置过滤(Pre-Filtering / Pushdown Filter):在向量召回阶段,将租户 ID、部门权限、生效状态等元数据作为约束条件与向量相似度计算同步执行。若采用召回后过滤(Post-Filtering),极易因高维召回结果被大面积权限过滤剔除,造成严重的召回空洞(Recall Vacuum)——请求了 Top-20,过滤后只剩 3 条,模型的证据基础严重不足。在 pgvector 这类单体方案里,前置过滤直接体现为一条 SQL:
SELECT id, content, embedding <=> :query_vec AS distance
FROM policy_documents
WHERE tenant_id = :tenant_id
AND dept_scope = ANY(:dept_scope)
AND status = 'published'
ORDER BY embedding <=> :query_vec
LIMIT 20;
- 版本一致性与原子切换:新版《费用管理办法》发布后,索引别名(Alias)的切换必须以原子操作完成。实践中常用 Blue-Green 索引策略:新版本索引构建完成后,将 Alias 从旧索引原子切换至新索引,旧索引延迟删除。若切换非原子,则可能出现搜索结果指向已作废旧政策的"僵尸链接"问题。
5. 分层架构选型路线¶
检索层的架构选型应随数据规模与业务复杂度分阶段演进:
- 阶段一——混合单体方案:业务起步期,推荐使用 pgvector 等数据库扩展。在同一关系数据库内存储业务结构化元数据与高维向量,实现事务级一致性,消除多库同步负担。适用于数据规模在百万级以下、并发压力中等的场景——公司内部制度知识库这类文档量在几千到几万篇的场景,pgvector 通常够用。
- 阶段二——专用检索层分离:数据规模达千万级且并发度高时,引入独立的 Elasticsearch / OpenSearch 承载全文检索,引入 Milvus / Pinecone 承载专业向量检索,实现读写算力的物理隔离。此阶段需额外建设索引同步管线(通常基于 MQ),并承担多存储系统间的最终一致性治理成本。
6. 故障排查序列¶
- 查索引链状态:异步索引 Worker 是否存在积压?Refresh 任务是否正常执行?Mapping 变更是否导致新文档字段(比如新增的
dept_scope)未被正确索引? - 查查询解析(Query DSL):分词结果是否符合预期("差旅报销"有没有被错误切分)?权重(Boost)分配是否引入了噪音干扰?是否存在分析器(Analyzer)与写入时不一致的情况?
- 查召回覆盖率:是关键词无法命中(分词器配置问题),还是向量检索发生了语义漂移(Embedding 模型质量或维度选择问题)?Hybrid Search 中两路召回的权重是否合理?
- 查资源瓶颈:Shard 是否过大导致查询长尾?JVM 堆内存是否因 Field Data 缓存过大而频繁触发 Full GC?Segment 数量是否过多导致查询遍历开销过大?
核心结论: 搜索层是业务真相的索引投影,而非数据库的实时镜像。健壮的检索架构以精确/模糊边界的清晰划分为前提,以异步索引流水线维护数据时效性,以检索层的权限硬约束作为多租户安全的最终防线,并通过混合检索与 Rerank 的多阶段过滤实现召回质量与计算成本的工程平衡。
面试怎么答:面试官问"数据库里有数据但搜不到",先定位再分层:先查索引链有没有积压、Refresh 有没有正常跑,再查是分词器没切对词还是向量语义漂移了。问 RAG 召回为什么不能只用向量检索,说清楚向量对编号、金额这类结构化强特征表征弱,工业界标准做法是 BM25 + 向量的 RRF 混合检索,k 通常取 60。问权限怎么做,一句话说完:过滤必须下推到检索阶段和向量相似度计算同步执行,不能等召回结果出来再过滤,否则 Top-20 过滤完只剩 3 条,模型证据不够用。