假设你是新上任的图书馆管理员,书架上堆着几十万本书。某天一位研究员跑来问:“我想借所有提到'量子计算'的书,能帮我找出来吗?”如果按书架一本本翻,你得加班到天亮。聪明的做法是什么呢?——下班前你连夜做了一套“索引卡片柜”:把书里出现过的关键词抄成一张张卡片,按字母排序,每张卡片背后写着书单、出现次数和页码。从此再有人问“量子计算”,你抽出卡片,秒回书单。 倒排索引(Inverted Index)就是 Elasticsearch 的“卡片柜”。它不像关系型数据库那样“先找书、再翻内容”,而是提前把“每个词出现在哪些文档”登记成表。配合分段存储、FST 压缩、近实时刷新等机制,ES 才能在万亿级文档中毫秒级返回全文检索结果。这篇文章就用图书馆的故事,带你拆解这套索引的全部秘密。

岗位关联

对搜索工程师而言,倒排索引是 ES 性能的根基——理解了它,才能解释“为什么写入要等一秒”,为查询优化、集群调优和故障排障找到正确方向。

目录
  1. 可视化演示
  2. 索引概念:从翻书到翻卡片
  3. 倒排结构:一张卡片,三重信息
  4. 核心流程:分三步走完一次搜索
  5. Segment机制:不可变的“新书册子”
  6. 提交与更新:登记簿与流水账
  7. FST压缩:把卡片标签折叠起来
  8. 近实时性:先上架,后编目
  9. B+树对比:书架与卡片柜各有所长
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示ES倒排索引的Term→Document映射结构、Segment不可变段与Commit Point机制、以及倒排索引与B+树索引的结构对比,切换模式查看不同层面。

本节配有交互动效(进入视口后自动加载,约 3KB)。

倒排索引:词项到文档的映射 文档分词后建立Term→DocList映射 Posting List记录包含该词项的文档ID Term → Posting List → Doc IDs 倒排索引实现O(1)级别的全文检索。

索引概念:从翻书到翻卡片

你接手图书馆第一天,就收到一个刁钻需求:把所有提到“数据仓库”的书找出来。书架是按编号排的,编号和内容毫无关系,你只能拿起第一本,从第一页翻到最后一页,做满记号再放下——几十万本翻完,天都亮了。你意识到:书架适合“按编号找书”,却回答不了“按内容找书”。

于是你想了个聪明办法:与其每次翻书,不如提前把“书里有哪些词”登记起来。正着登记,以每本书为开头,记“《数据库大全》包含:数据库、索引、查询……”;另一种做法是倒过来,以每个词为开头,记“数据库:出现在《数据库大全》《存储漫谈》……”。第二种,就是倒排索引。

在 Elasticsearch 中,文档(documents)先交给分词器切成词条(terms),系统为每个词条维护一个倒排表。查询时只需在词条字典里定位关键词,再取出倒排表,就能瞬间知道命中哪些文档,而不用逐篇全文匹配。这就是“反向映射”的精髓:把“文档→词”的对应翻转成“词→文档”。

现在你再遇到“找数据仓库相关的书”这种需求,只要翻开那张写着“数据仓库”的卡片,背后就是完整书单。从“一本本翻书”到“一翻卡片就有”,这就是倒排索引带来的质变。

Posting(t)={d∣t∈Terms(d)}\displaystyle \text{Posting}(t) = \{ d \mid t \in \text{Terms}(d) \}
通俗地说:词条 t 的倒排表,就是所有包含 t 的文档组成的集合——书单上每一本都不是随便写的,必须是“书里真的出现过这个词”。
  1. Posting(t) 表示倒排表词条 t 对应的倒排表,也就是“关键词 t 的书单”,列出所有讲过这个词的文档编号
    Posting(t)\displaystyle \text{Posting}(t)
  2. d 表示一篇文档文档的编号,对应图书馆里的一本书
    d\displaystyle d
  3. Terms(d) 表示文档词条集合文档 d 分词后得到的全部词条集合,相当于一本书的“全书词汇表”
    Terms(d)\displaystyle \text{Terms}(d)
  4. t ∈ Terms(d) 判定收录条件词条 t 出现在文档 d 中,这是书单收录这本书的唯一条件
    t∈Terms(d)\displaystyle t \in \text{Terms}(d)

倒排结构:一张卡片,三重信息

卡片柜搭好后,你发现光有“书单”还不够。读者常追问:“这几本书里,哪本讲'数据库'讲得最细?”如果卡片只写书名,你还是答不上来。于是你给每张卡片背面的每本书旁,记上两个数字:这个词在这本书里出现了几次、分别在第几章第几页。

这就是倒排结构的三要素:Term Dictionary 是卡片的“标签区”,所有关键词按字典序排列,方便快速定位;Posting List 是卡片背面的“书单区”,记录文档编号;Posting 里还带词频与位置。三者合起来,才构成一张完整的倒排卡片。

技术上,ES 的 Posting List 不止记录 docID,还记录 term frequency(词频,用于相关度打分)和 positions(词位置,用于短语查询与高亮)。Lucene 还进一步用跳表、位图等结构压缩倒排表,让“书单”更省空间、遍历更快。

现在卡片柜每一格都能回答三个层次的问题:这本书有没有提到(编号)、讲得多不多(词频)、讲到哪(位置)。读者再刁钻的“细节追问”,你翻翻卡片背面就有答案。

Posting(t)={(d,ft,d,post,d)}\displaystyle \text{Posting}(t) = \{ (d, f_{t,d}, \text{pos}_{t,d}) \}
一张卡片背后不再是光秃秃的书名,而是“文档编号 + 出现了几次 + 出现在哪些位置”的完整档案。
  1. d 记录文档编号书单上的第一列,指明词条出现在哪本书
    d\displaystyle d
  2. f 表示词频词条 t 在文档 d 中出现的次数,出现越多通常相关度越高
    ft,d\displaystyle f_{t,d}
  3. pos 表示位置列表词条 t 在文档 d 中出现的具体词位置,短语查询与高亮都靠它
    post,d\displaystyle \text{pos}_{t,d}
  4. 三元组构成完整档案每个三元组 (d, f, pos) 是卡片背面的一条记录,三者缺一不可
    (d,ft,d,post,d)\displaystyle (d, f_{t,d}, \text{pos}_{t,d})

核心流程:分三步走完一次搜索

一个读者走到柜台:“我想找同时讲'搜索引擎'和'倒排索引'的书。”你抽出两张卡片:一张写着“搜索引擎”,一张写着“倒排索引”。把两张书单摆在一起,找出都出现的编号——这就是查询的“与”运算。

一次完整搜索有四步:分词(把 query 切成词条)、构建(写入时已经建好索引)、匹配(查词典→取倒排表→求交集/并集)、排序(按相关性打分返回 Top N)。对用户来说这只是眨眼之间,对系统来说是一场流水线作业。

布尔查询是基础。多个词条按 AND 处理时,就是对各倒排表做集合交集;OR 则是并集。实际生产环境还会用 BM25 打分排序:词频越高、文档越短,得分越高,这就是“结果排序”环节的职责。

有了卡片柜,任何复杂问题都被拆成“翻哪几张卡片、怎么合并书单”的小事。图书馆的服务台从此再也不怕刁钻提问——分词、查卡、合并、排序,四步流水线各司其职。

R(q)=⋂t∈qPosting(t)\displaystyle R(q) = \bigcap_{t \in q} \text{Posting}(t)
多词查询就是翻出每张卡片,再求交集(AND)——只有同时出现在所有卡片里的文档才会进入结果。
  1. q 表示查询用户输入经过分词后的词条集合,相当于读者报出的一组关键词
    q\displaystyle q
  2. Posting(t) 取出每张书单为查询中的每个词条取倒排表,就像抽出对应卡片
    Posting(t)\displaystyle \text{Posting}(t)
  3. ⋂ 表示集合交集多个书单取共同出现的文档,对应 AND 语义
    ⋂t∈q\displaystyle \bigcap_{t \in q}
  4. R(q) 得到查询结果集合所有词条都出现的文档才是最终结果
    R(q)\displaystyle R(q)

Segment机制:不可变的“新书册子”

图书馆每天都有新书到货。最初你把新书硬塞进旧册子重新编目,结果每加一本书,几十万张卡片就要重写,图书馆直接瘫痪。你痛定思痛:新书不再改旧册,而是装订成一本新册子,单独上架。

ES 的每个分片(shard)由多个 Segment 组成。Segment 一旦生成就不可修改(immutable),这带来三个好处:无需加锁,并发读安全;文件缓存友好,热数据命中率高;崩溃恢复简单,已落盘段不用重写。代价是多次写入会产生很多小段,查询时要跨段汇总结果。

后台的 merge 线程会定期把小段合并成大段,减少段数量,同时真正清理被标记删除的文档。合并是纯后台操作,但会占用 IO,所以 ES 提供了按段数量、按段大小等触发策略,让管理员能控制“多久合并一次、合并到什么规模”。

就像图书馆每天闭馆后把散落的小册子并成大册、顺手清掉破损书页,合并让书架永远保持“恰到好处的整洁”,查询效率也稳定在最佳状态。

Segments(t)=S1∪S2∪⋯∪Sn  ⇒  Smerged\displaystyle \text{Segments}(t) = S_1 \cup S_2 \cup \cdots \cup S_n \;\Rightarrow\; S_{\text{merged}}
查询时所有 Segment 的书单要合并起来看;后台把若干小段并成一个大段后,段数变少,汇总成本下降。
  1. S1 到 Sn 表示多个分段查询时并列存在的若干 Segment,每个都有一份自己的倒排表
    S1∪S2∪⋯∪Sn\displaystyle S_1 \cup S_2 \cup \cdots \cup S_n
  2. ∪ 表示跨段汇总同一词条的书单分布在各段中,查询需要合并所有段的命中
    ∪\displaystyle \cup
  3. merged 表示合并后的大段后台把小段合并为一个新段,覆盖旧的多个段
    Smerged\displaystyle S_{\text{merged}}
  4. ⇒ 表示合并的方向段数从多到少,查询效率随之提升
    ⇒\displaystyle \Rightarrow

提交与更新:登记簿与流水账

一次断电事故让管理员惊出一身冷汗:临时架上几十本新书“凭空消失”,因为它们只存在于记忆里,没有登记。为了让数据万无一失,你立了两本账:一本“入库登记簿”,记录哪些册子正式盖章入库(已落盘);一本“流水账”,记住每次“书到了”但还没来得及盖章的操作。

对应 ES:Commit Point 是登记簿,记录所有已落盘的 Segment 及文件信息;写入先进内存 buffer 和 translog(流水账),当触发 flush 时,buffer 生成新 Segment 并 fsync 落盘,同时更新 Commit Point。translog 本身也会定时 fsync 到磁盘,防止内存丢失。

崩溃恢复时,ES 先根据最近一次 Commit Point 找到已落盘的数据,再重放 translog 里尚未提交的写入,从而做到不丢数据。这正是“先写日志、再改数据”的 WAL(Write-Ahead Logging)思想的体现:流水账永远是第一道保险。

有了登记簿和流水账,哪怕半夜突然断电,第二天你也能把书一本不差“找回来”。对数据密集型系统来说,这种双账本机制就是持久化的定心丸。

D恢复=Dcommit⊕Translog\displaystyle D_{\text{恢复}} = D_{\text{commit}} \oplus \text{Translog}
崩溃恢复 = 从登记簿上的已落盘数据出发,再把流水账里的操作重放一遍,补回丢失的写入。
  1. D_commit 表示已提交数据最近一次 Commit Point 之后已落盘的全部数据
    Dcommit\displaystyle D_{\text{commit}}
  2. Translog 表示流水账记录尚未提交的写操作序列,可追加、可重放
    Translog\displaystyle \text{Translog}
  3. ⊕ 表示重放操作把流水账中的写操作按顺序重新执行一遍
    ⊕\displaystyle \oplus
  4. D_恢复 表示最终数据崩溃重启后恢复出的完整数据,写入一条不少
    D恢复\displaystyle D_{\text{恢复}}

FST压缩:把卡片标签折叠起来

卡片柜越来越大,几十万张卡片堆满了三个房间。管理员想省钱省空间,仔细观察发现很多卡片标签重叠:“数据库”、“数据仓库”、“数据结构”都共享前缀“数据”。与其每张卡片都完整写一遍,不如做一个“树状折叠柜”:公共前缀只写一次,后面的分支各自展开。

ES 的词典(Term Dictionary)必须常驻内存才能快速定位词条,而海量词条会让内存爆炸。Lucene 用 FST(Finite State Transducer,有限状态转换器)来压缩词典:它把词条集合编码成一张有向无环图,共享前缀与后缀,既大幅节省内存,又能在图上做精确查找和前缀查询。

FST 的构建是贪心算法:按字典序插入词条,尽量复用已有路径,最终得到最小化的状态机。查询时从起点沿着字符边走,走完即找到词条及其对应的 value(如 docID 区间)。它是无损的,不会因为压缩丢掉任何一个词,这也是“快速查找”的底气所在。

换用折叠柜后,房间空出一大半,找卡片的速度反而更快——因为路径更短了。内存省了、查询快了,FST 是 ES 在万亿词条下依然轻盈的秘密武器。

Saved=∑t∈T∣t∣−∣FST(T)∣\displaystyle \text{Saved} = \sum_{t \in T} |t| - |\text{FST}(T)|
节省的内存 = 压缩前所有词条的字节总数 − FST 形态下的字节总数,公共前缀共享得越多,节省越明显。
  1. T 表示全部词条集合词典中需要存储的所有词条
    T\displaystyle T
  2. |t| 表示词条字节数单个词条在压缩前占用的存储大小
    ∣t∣\displaystyle |t|
  3. |FST(T)| 表示压缩后大小整棵 FST 状态图的总存储量
    ∣FST(T)∣\displaystyle |\text{FST}(T)|
  4. Saved 表示节省空间压缩前后的差值,也是内存压力的缓解量
    Saved\displaystyle \text{Saved}

近实时性:先上架,后编目

新书上架流程很长:编目、盖章、入库、登记,一套走完往往要等很久。但读者等不及。于是你开了一个“新书快借区”:新书到货,先在内存里整理成临时小册子,每隔一秒统一摆上快借区,读者立刻能借到;至于盖章入库、登记账本,那是后台慢慢做的事。

ES 的写入默认先进内存 buffer,每 refresh(默认 1 秒)把 buffer 生成一个新 Segment,并让这段数据对搜索可见。所以“写入到可搜”的时延约为一个 refresh 周期,这就是近实时(Near Real-Time)的含义——不是实时,但足够快。

如果业务要求“写入后立刻可见”,可以把 refresh_interval 调小甚至手动 refresh,但这会频繁生成小 Segment,拖累写入吞吐。反之,批量导入时把 refresh 调大(如 30 秒),写入性能显著提升。真正的持久化仍要靠 translog fsync 与 flush 落盘完成。

“先上架,后编目”的策略让图书馆在“及时可用”与“严谨归档”之间找到了平衡——你还可以根据客流(写入压力)灵活调整快借区的刷新频率,这正是 ES 近实时设计的精髓。

t可见≈refresh_interval\displaystyle t_{\text{可见}} \approx \text{refresh\_interval}
数据“可被搜索”的等待时间大约等于一个 refresh 周期,默认大约 1 秒。
  1. t_可见 表示可见延迟从写入到可被搜索的等待时间
    t可见\displaystyle t_{\text{可见}}
  2. refresh_interval 表示刷新间隔定期把内存 buffer 变成可搜索 Segment 的周期,默认 1 秒
    refresh_interval\displaystyle \text{refresh\_interval}
  3. ≈ 表示近似相等实际延迟还受 buffer 大小、线程调度等因素影响
    ≈\displaystyle \approx
  4. 权衡点间隔越小越实时,但小 Segment 越多;间隔越大写入越快,但可见越慢
    延迟↑⇔吞吐↓\displaystyle \text{延迟} \uparrow \Leftrightarrow \text{吞吐} \downarrow

B+树对比:书架与卡片柜各有所长

你发现隔壁老图书馆用的是另一种体系:每本书有固定编号,按编号顺序排满书架,想找编号 1000 到 2000 之间的书,沿着书架走一趟就有;但想找“提到某词的书”,依然得一本本翻。而你的卡片柜呢?找关键词秒回,却回答不了“编号 1000 到 2000 之间有哪些书”这种范围问题。

关系型数据库(如 MySQL InnoDB)用 B+树做主索引:叶子节点有序存放行数据,擅长范围查询、排序、精确匹配;ES 的倒排索引则把“词→文档”映射前置,擅长全文检索,“哪个词出现在哪些文档”是它的主场。两者解决的其实是不同维度的问题,这就是“原理差异”的本质。

B+树查询复杂度是 O(log n + k),n 是记录数、k 是结果数;倒排索引查询要先在 FST 词典中定位词条,再取倒排表,复杂度与命中结果规模相关,与文档总数基本无关。所以文档越多,倒排索引的“免扫描”优势越明显。

书架与卡片柜并不冲突:成熟系统往往两者并用——MySQL 用 B+树管行、ES 用倒排管词,再由业务层协调。理解了各自的边界,你才能在设计检索系统时让它们各司其职。

B+树:O(log⁡n+k);倒排:O(L+R)\displaystyle \text{B+树}: O(\log n + k); \quad \text{倒排}: O(L + R)
B+树查询开销随表规模和结果数增长;倒排查询开销与文档总量基本解耦,主要看命中的结果规模。
  1. log n 表示查找层数B+树从根到叶的层数,与记录总数 n 的对数成正比
    log⁡n\displaystyle \log n
  2. k 表示结果记录数命中结果的数量,输出阶段线性扫描不可避免
    k\displaystyle k
  3. L 表示词典查找开销倒排查询先查词典的成本,FST 下近似于词条长度
    L\displaystyle L
  4. R 表示倒排表规模命中的倒排表大小,决定倒排查询的快慢
    R\displaystyle R

AI 应用场景

电商商品搜索

阶段:数据索引与检索

解决的问题:用户在电商平台输入“白色 连衣裙 夏季”,传统数据库用 LIKE '%连衣裙%' 做全表扫描,数据量过千万时耗时长且无法评分排序。AI 搜索系统先用分词器切出词条,再走倒排索引查词典、取倒排表、按 BM25 打分,毫秒级返回并排序。

具体案例:某服饰电商每秒处理 3000 次搜索请求,商品文档每天更新 200 万条。用 ES 倒排索引后,搜索平均响应从 1.8 秒降到 45 毫秒,同时利用 Segment 合并策略保障写入吞吐,大促期间稳定扛住流量高峰。

日志检索与可观测性平台

阶段:海量日志的实时索引与检索

解决的问题:传统方式按行存日志文件,排查“某个服务在 10:00 到 10:05 之间报了多少次 OOM”需要 grep 全部日志,效率极低。ELK 技术栈用 Logstash 采集日志,ES 为每条日志建立倒排索引,字段如 level、message 均可快速过滤。

具体案例:某金融公司每天产生 50 TB 日志,使用 ES 按天建索引、配合近实时 refresh 让故障日志秒级可查。SRE 通过关键字 + 时间范围组合查询,几分钟内定位线上故障根因。

企业知识库与合同全文检索

阶段:非结构化文档的语义检索前置

解决的问题:法律合同、规章制度等 PDF 文档散落在共享盘里,员工检索全靠文件名。ES 把文档解析成文本并构建倒排索引,配合 FST 压缩让百万级词条的词典常驻内存,实现“全文任意词秒级定位”。

具体案例:某大型企业将 80 万份合同导入 ES,法务人员输入“违约责任 提前终止”,系统分词后走倒排索引联合查询,不仅返回相关合同编号,还能利用词位置高亮命中片段,找合同从半天缩短到几秒钟。

Python 演示

python48 行
# -*- coding: utf-8 -*-# 迷你倒排索引: 用 Python 字典实现 "词条 -> 文档" 的翻转映射import refrom collections import defaultdict  class MiniInvertedIndex(object):    def __init__(self):        self.doc_texts = {}                # doc_id -> 原文        self.term_docs = defaultdict(set)  # term -> 命中的 doc_id 集合        self.term_total = defaultdict(int) # term -> 全局出现次数     def add_doc(self, doc_id, text):        self.doc_texts[doc_id] = text        tokens = re.findall(r"\w+", text.lower())        local_count = defaultdict(int)        for token in tokens:            local_count[token] += 1        for token, freq in local_count.items():            self.term_docs[token].add(doc_id)            self.term_total[token] += freq     def query_and(self, query_text):        tokens = re.findall(r"\w+", query_text.lower())        hit = None        for token in tokens:            cur = self.term_docs.get(token, set())            hit = cur if hit is None else hit & cur        return sorted(hit) if hit else []     def top_terms(self, limit=5):        items = sorted(self.term_total.items(), key=lambda kv: kv[1], reverse=True)        return [(t, c) for t, c in items[:limit]]  if __name__ == "__main__":    idx = MiniInvertedIndex()    docs = {        1: "Elasticsearch is a distributed search engine",        2: "A search engine builds an inverted index",        3: "The inverted index powers full text search",    }    for did, content in docs.items():        idx.add_doc(did, content)    print("query 'search engine':", idx.query_and("search engine"))    print("query 'inverted index':", idx.query_and("inverted index"))    print("query 'database':", idx.query_and("database"))    print("top terms:", idx.top_terms())
目的:用约 60 行 Python 复刻 ES 倒排索引的核心环节——分词、构建“词条→文档”映射、布尔 AND 查询求交集,运行即可看到词条命中的文档编号。
  1. self.term_docs[token].add(doc_id)核心一步:把词条 token 映射到文档 doc_id,这就是“倒排”关系的建立。
  2. re.findall(r"\w+", text.lower())简易分词:按单词切分并统一小写,模拟 ES 的 analyzer 分词器。
  3. local_count[token] += 1统计词频,对应正文里“卡片背面的次数记录”。
  4. hit = cur if hit is None else hit & cur多个词条做集合交集,实现布尔 AND 查询。
  5. idx.query_and("inverted index")复现“翻两张卡片、合并书单”的完整查询过程。

常见误区

误区我以为倒排索引就是把文档内容反转过来存放,就像把句子从后往前写一遍,这样就能加快搜索。

正解倒排索引翻转的是“映射方向”,不是文档内容。文档原文始终原样存储;变的是索引结构从“文档→包含哪些词”翻转为“词→出现在哪些文档”。就像图书馆不会把书倒着放,只是新增了一套按关键词组织的卡片柜。

为何常见:名字里的“倒”字极具迷惑性,初学者容易望文生义,把“方向倒转”误解成“内容倒置”。

误区我以为像 MySQL 提交事务一样,ES 写入成功后马上就能被搜索到,毕竟接口都返回成功了。

正解ES 是近实时(NRT)系统,写入先进内存 buffer,要等下一个 refresh 周期(默认约 1 秒)生成新 Segment 后才对搜索可见。接口返回成功只代表写入已接收,不代表已可见。需要即时可见可调小 refresh_interval 或调用 refresh API,但要付出写入性能代价。

为何常见:大多数人对数据库的“提交即见”习以为常,没有意识到内存 buffer 与可搜索索引之间还隔着一层“刷新”。

误区执行 delete 后,磁盘占用应该立刻下降,就像删文件一样干脆。可我用 df 一看,空间一点没少。

正解ES 的删除是“标记删除”:先在文档上打删除标记,查询时过滤掉,真正的物理清除要等后台 Segment 合并时才会发生,磁盘空间也随之释放。所以删除后空间不降是正常现象。

为何常见:delete API 立即返回成功,误导使用者以为物理删除已完成,忽略了 Segment 不可变这个前提。

误区我以为 Segment 越多,数据划分越细,并行查询越快,所以写入频繁时小段多也没关系。

正解恰恰相反,查询要扫描所有 Segment 再跨段汇总,段数越多汇总开销越大,查询会变慢。后台 merge 线程把小段合并成大段正是为了减少段数。合并虽然耗 IO,但从整体上让查询更稳定高效。

为何常见:看不到“段”这个中间层,只看到写入量增长,容易把并发写与查询性能画上错误等号。

误区一听“压缩”我就联想到图片压缩会损失画质,担心 FST 压缩后某些词条就搜不到了。

正解FST 是无损压缩,它只是把词条集合编码成共享前缀/后缀的状态图,所有词条都能精确检索,还天然支持前缀查询。它压缩的是“存储冗余”,不是“词条内容”。

为何常见:日常接触的 JPEG、MP3 等有损压缩先入为主,让人对一切“压缩”都产生信息丢失的担忧。

面试题

Q1. 倒排索引的核心作用是把下面哪种关系翻转?

单选题 · easy

  • A.文档到词条的映射关系← 正确答案
  • B.词条到文档的映射关系
  • C.数据库到表的映射关系
  • D.表到字段的映射关系

答案:A。倒排索引的精髓是把“文档→包含哪些词”翻转成“词→出现在哪些文档”。选 A。B 是翻转后的结果方向而非翻转动作本身;C、D 属于数据库编目范畴,与倒排索引的“词文档映射”无关。

Q2. 词条“数据库”的倒排表中不会记录下列哪项信息?

单选题 · easy

  • A.包含该词的文档编号
  • B.该词在文档中的词频
  • C.该词在文档中的位置
  • D.该词的字面长度← 正确答案

答案:D。倒排表(Posting List)记录文档编号、词频、位置,分别用于定位、打分和高亮。词条的字面长度属于词典管理的元信息,不属于倒排表内容,故选 D。

Q3. ES 中 Segment 具有不可变性,这一设计带来的最大好处是?

单选题 · mid

  • A.可以支持原地更新词条
  • B.无需加锁并发安全读← 正确答案
  • C.减少磁盘空间占用
  • D.加快数据写入速度

答案:B。Segment 不可变意味着没有并发写冲突,读取无需加锁、缓存稳定,这是 ES 高并发读的基石。A 与不可变性相悖;C、D 是不可变性之外的间接收益,并非最大好处。

Q4. 关于 ES 删除文档的说法,正确的是?

单选题 · mid

  • A.删除后磁盘空间立即释放
  • B.删除只打标记合并时清除← 正确答案
  • C.删除会重写整个倒排索引
  • D.删除后数据立刻不可恢复

答案:B。ES 的 delete 是延迟删除——先加删除标记,查询时过滤,段合并时才物理清除并释放空间。A 是常见误解;C 代价过大不可行;D 错误,合并前数据仍保留。

Q5. FST 压缩能大幅节省内存,主要利用了词条的哪些特点?

多选题 · mid

  • A.存在公共前缀可以共享← 正确答案
  • B.存在公共后缀可以合并← 正确答案
  • C.所有词条长度完全相同
  • D.词条出现频率完全相同

答案:AB。FST 把词典编码成有向无环图,公共前缀共享路径、公共后缀合并状态,从而实现无损压缩。C、D 不是词条的客观规律,也不构成压缩依据。

Q6. 默认配置下,ES 写入一条新文档后大约多久可被搜索到?

单选题 · mid

  • A.毫秒级实时可见
  • B.大约 1 秒后刷新可见← 正确答案
  • C.必须等 flush 落盘
  • D.必须等段合并完成

答案:B。默认 refresh_interval 为 1 秒,写入先进内存 buffer,refresh 时生成新段并对搜索可见。A 太快不可能;C 是 flush 的职责;D 合并与可见性无关。

Q7. 关于 translog 的作用,下列说法最准确的是?

单选题 · hard

  • A.记录已提交段的完整内容
  • B.在崩溃后重放未提交写入← 正确答案
  • C.压缩内存中的倒排索引
  • D.加速桶聚合查询的速度

答案:B。translog 是写前日志(WAL),记录尚未提交的写入,崩溃后重放它补齐数据。A 是 Segment 的职责;C、D 与 translog 毫无关系。

Q8. 与 B+树相比,倒排索引在处理哪类查询时更有优势?

单选题 · hard

  • A.主键值等于某值的点查
  • B.对某字段做范围查询排序
  • C.关键词全文检索与统计← 正确答案
  • D.多表连接与事务回滚

答案:C。倒排索引的“词→文档”映射天然适合全文检索与词频统计,文档总量越大优势越明显。A、B 是 B+树的强项;D 属于数据库事务层,与索引类型关系不大。

Q9. 下列哪些属于倒排索引的组成部分?

多选题 · mid

  • A.词条字典 Term Dictionary← 正确答案
  • B.倒排表 Posting List← 正确答案
  • C.事务日志 Redo Log
  • D.主键索引 B+树

答案:AB。倒排索引两大核心组件是词典(Term Dictionary)和倒排表(Posting List)。C 是数据库日志,D 是关系库索引,均不属于 ES 倒排索引的构成。

Q10. 下列关于 Segment 合并的说法,正确的有哪些?

多选题 · hard

  • A.合并由后台线程自动执行← 正确答案
  • B.合并后仍必须保留所有小段
  • C.合并时真正清除被删文档← 正确答案
  • D.合并一定会拖慢查询速度

答案:AC。合并由后台线程自动进行,合并时真正清除删除标记并释放磁盘,选 A、C。B 相反——合并的目的就是减少段数,无需保留小段;D 错误——合并完成后查询更快,虽然合并过程会消耗 IO。