1.1 一次搜索的幕后:倒排索引的直觉


文档摘要

1.1 一次搜索的幕后:倒排索引的直觉 本节摘要:倒排索引(inverted index)是 Elasticsearch 搜得快的根本原因——它把"从文档找词"的正排思路反转成"从词找文档"。本节用一次真实的搜索事故讲清这个结构的方向差异,手算一张迷你倒排表,并解释为什么分词质量决定了搜索质量。这是 doc-1001 旅程的第一站,也是后面所有章节的解释起点。 从一次搜索事故说起 去年冬天,一个工单系统的值班群炸了。运营反馈"搜索退款两个字要等十几秒",DBA 查了慢查询日志,罪魁是这条 SQL: 问题不在 SQL 写得差,而在数据结构本身。B+ 树索引擅长的是"按有序值快速定位"——前缀匹配能用上索引,夹在中间的百分号让它彻底失效,只能退化为逐行扫描。

1.1 一次搜索的幕后:倒排索引的直觉

本节摘要:倒排索引(inverted index)是 Elasticsearch 搜得快的根本原因——它把"从文档找词"的正排思路反转成"从词找文档"。本节用一次真实的搜索事故讲清这个结构的方向差异,手算一张迷你倒排表,并解释为什么分词质量决定了搜索质量。这是 doc-1001 旅程的第一站,也是后面所有章节的解释起点。

从一次搜索事故说起

去年冬天,一个工单系统的值班群炸了。运营反馈"搜索退款两个字要等十几秒",DBA 查了慢查询日志,罪魁是这条 SQL:

-- 模糊匹配无法使用 B+ 树索引,只能全表扫描 SELECT id, title FROM ticket WHERE content LIKE '%退款%'; -- 80 万行数据,执行计划显示 rows=800000,耗时 11.4 秒

问题不在 SQL 写得差,而在数据结构本身。B+ 树索引擅长的是"按有序值快速定位"——前缀匹配能用上索引,夹在中间的百分号让它彻底失效,只能退化为逐行扫描。数据库的行存储是正排结构:先有文档,文档里包含若干词。拿着词去找文档,等于把图书馆里每本书逐页翻开检查。

真正的搜索引擎走了另一条路:提前把"哪个词出现在哪些文档里"整理成一张表。读者找"退款",管理员不翻书,直接抽出"退款"这张卡片,卡片背面写着所有出现过的书架号。这就是倒排索引——方向反过来,从词指向文档。

手算一张倒排表

光说方向反转还是抽象,我们把三条工单亲手拆一遍。doc-1001 的内容是"用户申请退款,处理速度太慢",doc-1002 是"退款已到账",doc-1003 是"发票抬头开错了"。中文按最粗粒度切词后,得到下面这张表(真实引擎还会记录词频与位置,这里先省略):

{ "词典与倒排表(概念性简化)": { "用户": { "文档": ["doc-1001"] }, "申请": { "文档": ["doc-1001"] }, "退款": { "文档": ["doc-1001", "doc-1002"] }, "处理": { "文档": ["doc-1001"] }, "速度": { "文档": ["doc-1001"] }, "太慢": { "文档": ["doc-1001"] }, "到账": { "文档": ["doc-1002"] }, "发票": { "文档": ["doc-1003"] }, "抬头": { "文档": ["doc-1003"] } } }

搜"退款"时引擎做的事:查词典定位"退款",读出文档列表,完事。两次内存跳转,与库存了多少文档无关——这就是它和逐行扫描的本质差别。如果查询两个词,比如"退款 到账",就取两个列表做交集或并集,再按匹配程度排序。规模越大,差距越夸张。

两种结构的方向差异

维度 正排(行存储) 倒排(词到文档)
回答的问题 这条文档包含哪些词 这个词出现在哪些文档
擅长的操作 按主键取整行、事务、范围扫描 全文匹配、多词组合、相关性排序
模糊匹配代价 逐行扫描,随数据量线性变慢 查表,基本与数据量无关
典型代表 关系型数据库 Lucene、Elasticsearch

值得注意的细节是:倒排表里存的是切出来的词,不是原文。doc-1001 写的是"处理速度太慢",如果切词时切成"处理、速度、太慢",那么搜"太慢了"切出"太慢、了"仍能命中"太慢";可要是切词器压根不认识"太慢"这个词,把它切碎成单字,搜"速度慢"就永远命中不了。搜索质量的上限,在写入时就被分词器锁定了——这句话是第 3 章整章的伏笔。

倒排索引的结构

倒排索引的结构

引擎还会给每个词记录词频(出现次数)和位置(出现在第几个词位)。词频高的文档通常更相关——"退款"出现五次的工单,大概率比出现一次的更对题。第 5 章讲打分公式时,这两个数字会再次出场。

💡 关键直觉:把倒排索引想成图书馆的卡片目录。书是文档,卡片是词条,卡片背后的书架号是文档编号。管理员从不翻书找词,只翻卡片。

考核与自测

考核知识点清单

考核点 达标标准
方向反转 用一句话说清正排与倒排各自回答的问题
手算倒排表 给三条文档与切词结果,写出词典与倒排列表
查表过程 说出从查询词到命中文档列表的两跳:定位词典、读出列表
性能边界 解释为什么查表代价与文档总量基本无关
附带信息 说出词频与位置分别供打分与短语匹配使用
分词上限 举一个切词不当导致永远搜不到的具体例子

动手验证:亲眼看一次倒排表

第 2 章的环境搭好后,词条向量接口能把一个字段的倒排登记直接打印出来。写入样例文档再执行:

GET tickets/_termvectors/doc-1001?fields=title

响应的 terms 一节列出每个词条的词频与位置——本节手算的那张表,就是引擎替你记录的版本。换一个切词粒度不同的分析器重写文档,再查一次,词条列表随之变化:"倒排表里只有切出来的词"从论断变成眼见为实,第 3 章的分词台也因此有了预习材料。

易错点补充

  • 把倒排索引当成"原文的另一种存储格式":它只存词到文档的映射与统计信息,原文在 _source 里,两者职责不同、互不替代。
  • 以为多词查询要重扫原文:多词组合是对多张倒排列表做交集或并集,全程不碰原文。
  • 用单字切分误判中文分词没问题:逐字兜底能让部分查询命中,但"速度慢"这类词被切碎后,相关性与打分都会失真,切词粒度是第 3 章的正题。
  • 以为词典和倒排列表建好就一劳永逸:它们随每次写入持续生长,分词器选错等于让不合适的词条源源不断进表,纠错成本随数据量上升。
  • 拿内存占用量质疑结构合理性:词典前缀压缩与倒排列表增量编码让结构远比想象紧凑,先量再说,别凭直觉否决。

本节要点回顾

  • 倒排方向:从"文档含词"反转为"词在哪些文档",搜索从扫描变成查表。
  • 性能边界:查表代价与文档总量基本无关,这正是 80 万行模糊查询救不回来、搜索引擎却毫秒响应的原因。
  • 分词决定上限:倒排表里只有切出来的词;切词不当,写入再规范也搜不到。
  • 附带信息:词频与位置随倒排表存储,是打分与短语匹配的原材料。

下一节把镜头拉远:这张倒排索引住在哪里、由谁保管——集群、节点、分片与副本的地图就在眼前。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U