2297 字
约 7 分钟
18
S13-A20 · 检索失败的三类归因:搜不到、搜不准、搜不全

用户反馈"搜索不好用"。这句话背后可能是三件完全不同的事:输入关键词没有结果(搜不到)、有结果但排在前面的不对(搜不准)、有结果但少了本该有的那条(搜不全)。三种问题的修法完全不同,而团队常常在"调相关性权重"上花了三周,实际问题出在数据没有入库。

归因是检索优化的第一步,也是最容易被跳过的一步。没有归因的优化就是猜,而搜索系统对猜测的反馈极其缓慢——一次权重调整要一周才知道好坏。


1. 三类失败的定义与判据

类型 判据 典型症状
搜不到 精确匹配存在但结果为 0 输入完整标题也没有命中
搜不准 结果包含目标但排名靠后 目标在第 3 页
搜不全 已知应命中的文档缺失 新数据搜不到

还有第四类常被单独提出的"搜不准",其实是产品问题而不是检索问题:用户输入的意思与库里的词不匹配(比如搜"退款"但库里叫"售后")。这类问题靠同义词与词典解决,不靠打分。

三者的检测方式不同:第一类靠"用原文搜",第二类靠"看排名位置",第三类靠"按 ID 反查"。

2. 搜不到的五种原因

原因 检查方式
分词把词切碎了 用分词工具看拆解结果
字段未建索引 查 mapping 设置
大小写/全半角未归一 用同一词的变体搜
停用词表误伤 查停用词列表
分析器与查询器不一致 对比 index/search analyzer

排查这一项有个很直接的命令:让系统把同一个词的写入与查询拆解都打印出来。两边结果一比,问题立刻可见,不需要猜。

// 对比写入与查询的分词结果,最容易定位"搜不到"
curl -s -XPOST "localhost:9200/_analyze" -H 'Content-Type: application/json'   -d '{"field":"title","text":"七天无理由退款"}'
curl -s -XPOST "localhost:9200/_analyze" -H 'Content-Type: application/json'   -d '{"analyzer":"standard","text":"七天无理由退款"}'
// 前者是写入视角,后者是查询视角;两组 token 对不上就是根因

第五项最隐蔽:写入时用标准分词、查询时用关键字分词,两边对同一个词的处理不同,于是"写进去的"和"查出来的"永远对不上。

// 反例:写入与查询分析器不一致
"title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "standard" }
// 写入被拆成细粒度词,查询按标准分词整体匹配 → 命中率极低

// 正例:查询用细粒度(召回优先),写入用粗粒度需谨慎
"title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }

3. 搜不准的四个来源

来源 说明
打分字段权重不当 标题与正文权重相同
缺业务加权 新品/高销量应提权
字段长度归一化过度 短文档被过度提权
同义词未配置 用户词与库内词不同

第三项是 BM25 的默认行为:短文档天然得分高,于是一条只有标题的记录可能压过一条完整的长文。修法是调整 b 参数或引入字段级长度归一。

// 加权:把"标题命中"与"正文命中"区分开
"query": { "multi_match": {
  "query": "退款",
  "fields": ["title^3", "summary^2", "content"],
  "type": "best_fields"
}}
// ^3 表示标题命中的权重是正文的 3 倍;系数需按样本调,不要凭感觉

4. 搜不全的五个原因

原因 检查方式
同步链路延迟或丢失 对账:库内条数 vs 索引条数
过滤条件误伤 用 ID 直查,逐步加过滤
分片与副本未分配 查集群健康
字段超长被截断 查 mapping 的 ignore_above
索引写入被拒绝 查 bulk 的错误响应

第二项(过滤条件误伤)在带权限或软删的系统里极常见:索引里有这条,但查询里带了一个"只查未删除"的条件,而该字段在索引里是旧值。这类问题的排查方式是从"按 ID 直查"开始,逐步加条件,看哪一步丢了结果。

第一项是"新数据搜不到"的主因。同步链路的失败往往是静默的:bulk 请求部分失败会返回 200,错误藏在响应体里,如果代码不检查就完全无感。

// 检查同步完整性:库内条数与索引条数对齐
mysql -N -e "SELECT COUNT(*) FROM orders WHERE deleted_at IS NULL"
curl -s "localhost:9200/orders/_count" | jq .count
// 差异持续存在 → 同步链路问题;差异随时间扩大 → 有失败未重试

5. 归因的操作流程

步骤 动作
1 取真实查询 从搜索日志里抽样,不要自己造
2 分类 按三类归因打标
3 定优先级 按查询量加权,而不是按抱怨声
4 单点验证 每类各修一处,看指标变化

归因的结果应当落成一张"评估集":每类各留几十条真实查询作为回归样本。此后任何调参都要在这套样本上跑一遍,否则无法判断"这次改动是变好还是变坏"。

第三步最关键:一条被抱怨十次的问题可能只占查询量的 0.01%,而某个占 20% 的查询类型没人抱怨——因为用户已经放弃搜索了。所以优先级要按查询量加权。

-- 从搜索日志里按"零结果"聚类,找出高频失败词
SELECT normalized_query, COUNT(*) AS n
FROM search_log
WHERE result_count = 0 AND ts > NOW() - INTERVAL 7 DAY
GROUP BY normalized_query
ORDER BY n DESC LIMIT 30;
-- 零结果率高且查询量大的词,才是真正该修的

6. 排错:五种"调了权重没效果"

现象 成因 处理方向
调整权重指标不变 问题在"搜不到"层 先做归因分类
修了一类另一类变差 权重是全局的 按查询类型分开调
离线指标好、线上差 离线样本与线上分布不同 用线上日志做评测集
新数据一直搜不到 同步链路静默失败 加对账与失败重试
某些词永远零结果 同义词/分词问题 补词典与同义词

排错时还有个高效手段:看零结果查询的"改写行为"——用户是否在零结果后立刻换词重搜。换词率高说明是"搜不到",不换词直接离开说明是"没找到想要的",两者的修法不同。

第三项是检索优化的经典陷阱:离线评测集往往由人工构造,分布比真实查询更"规范"。用真实日志建评测集,才能反映线上效果。

7. 边界:什么情况不必做精细归因

场景 为什么 做法
数据量极小(千级) 排序影响小 只保证能搜到
搜索是辅助入口 用户主要靠浏览 保证召回即可
查询高度同质 一类问题一种修法 直接修那一类
已用外部搜索服务 排序由服务方负责 只关注召回与数据同步

还有一条与数据质量有关的边界:检索的上限由数据决定。标题缺失、分类错误、描述为空的数据,再好的相关性算法也救不回来。所以在调排序之前,先看一眼"应命中的文档本身是否完整"。

判断口径一句话:用户搜不到东西时,会不会直接离开。会,三类归因都要做;不会,先把"搜不到"这一类修好就够。


8. 自测

  1. 三类失败各自的判据是什么?为什么检测方式不同?
  2. 写入与查询分析器不一致会造成什么后果?为什么它最隐蔽?
  3. 为什么短文档在默认打分下会天然占优?修法是什么?
  4. "新数据搜不到"最常见的原因是什么?为什么同步失败往往静默?
  5. 归因流程里第三步为什么按查询量加权而不是按抱怨次数?
  6. 离线评测集为什么容易给出偏乐观的结论?替代做法是什么?
S13-A20 · 检索失败的三类归因:搜不到、搜不准、搜不全
https://www.clxhxhhr.top/posts/6445/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。