倒排索引原理:ES 为什么快

22 May 2026 – wusfe · 4 min read

倒排索引原理:ES 为什么快

1. 正排索引 vs 倒排索引

想象你在翻阅一本技术书:

  • 正排索引(Forward Index) 就像书的目录:从"第 1 章 概述"翻到第 N 章,逐页查找某个术语。MySQL InnoDB 的 B+Tree 索引本质上也是正排——给定主键查出整行数据,但如果你要搜索"哪些行包含关键词 X",它必须全表扫描。
  • 倒排索引(Inverted Index) 就像书末的索引页:直接告诉你"Inverted Index 这个术语出现在第 23 页、第 87 页、第 156 页"。Elasticsearch 正是用这种方式,让关键词搜索从 O(n) 变为 O(1)。
特性 正排索引(MySQL B+Tree) 倒排索引(ES/Lucene)
核心结构 主键 → 行数据 Term → 文档 ID 列表
等值查询 O(log n),极快 更快,内存跳表
模糊/全文搜索 全表扫描,极慢 Term 匹配,极快
LIKE '%keyword%' 全表扫描 分词后直接命中
空间代价 较大(多份索引)
更新代价 原地更新 先删后增(段合并)

2. 三层核心结构

从逻辑到物理,Lucene 的倒排索引分为三层:

2.1 Term Dictionary(词典)

存储所有不重复的 Term,按字典序排列。查询时先在这里二分查找定位 Term。

2.2 Term Index(FST 前缀树)

纯粹在内存中对 Term Dictionary 建索引的树形结构。FST(Finite State Transducer)可以同时压缩键和值,大幅节省内存。给定前缀"inverted",FST 直接跳到词典中 inverted 所在区块,无需遍历整个 Term Dictionary。

查询 "search" 的过程:
  FST 前缀匹配 "sea" → 定位到词典中 "sea" 附近
    → 二分查找精确匹配 "search"
      → 获取 Posting List 指针

2.3 Posting List(Roaring Bitmap)

存储包含该 Term 的所有文档 ID。Lucene 用 FOR(Frame of Reference)编码 + Roaring Bitmap 双层策略压缩:

  • 文档 ID 差值编码后按高 16 位分桶
  • 桶内文档数 < 4096 则用有序数组,> 4096 则用位图
  • 这套方案在全内存和少量内存之间取得最佳平衡

3. 用两条文档演示构建过程

假设有两条文档:

POST _bulk
{ "index": { "_index": "blog", "_id": 1 } }
{ "title": "Elasticsearch 倒排索引原理" }
{ "index": { "_index": "blog", "_id": 2 } }
{ "title": "倒排索引与 Roaring Bitmap 优化" }

分词假设(ik 分词器):

文档 ID Terms
1 elasticsearch, 倒排, 索引, 原理
2 倒排, 索引, roaring, bitmap, 优化

倒排索引构建结果:

Term Posting List
elasticsearch [1]
倒排 [1, 2]
索引 [1, 2]
原理 [1]
roaring [2]
bitmap [2]
优化 [2]

当用户搜索"倒排索引"时:

GET blog/_search
{
  "query": {
    "match": { "title": "倒排索引" }
  }
}

ES 拿到倒排索引后计算的方式如下:

  1. 分词"倒排"→ PostingList [1,2],分词"索引"→ PostingList [1,2]
  2. 取交集 → [1,2] 都匹配
  3. 无需遍历任何文档,直接返回文档 1 和 2

再搜索"elasticsearch bitmap":

  1. "elasticsearch" → [1],"bitmap" → [2]
  2. 取交集 → 空(如果是 AND 逻辑),或取并集 → [1,2](OR 逻辑)

这就是倒排索引的核心威力——搜索复杂度与文档总数无关,只与关键词涉及的文章数成正比

4. 为什么 ES 不能替代 MySQL

倒排索引适合搜索,但不适合精确关联和事务:

场景 适合工具 原因
文章全文搜索 ES 倒排索引直接命中
订单与用户 JOIN MySQL ES 不支持高效 JOIN
聚合统计(GROUP BY) 视情况 ES 聚合更快但内存消耗大
ACID 事务 MySQL ES 没有跨分片事务

5. 踩坑案例

踩坑 1:将 ES 当 MySQL 用

实际项目中,有团队用 ES 存储用户订单并做多表关联查询。每次搜索都要跨多个父子关联类型,Nested Query 嵌套超过 3 层性能严重下降,GC 频繁,QPS 从 2000 骤降到 50。最终改为 MySQL 存储订单、ES 仅同步商品名+ID 做全文搜索,MySQL 回表补全数据,性能恢复正常。

踩坑 2:分词器选错导致搜不到

电商搜索"iPhone15",用 standard 分词器不会拆分驼峰词,iPhone15 被当成一个整体 term。用户搜"15"时命中不了。换成 ik_max_word 分词器,iPhone15 可以被拆分为 iphone15,搜索才正常工作。

PUT products
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      }
    }
  }
}

6. 总结

倒排索引的本质是以空间换时间:以每个 Term 为 Key 预建文档列表,查询时无需遍历全库,直接取出目标文档集合。ES 的强大正来自 Lucene 的 FST + Roaring Bitmap + Segment 三层架构,让海量数据的全文检索达到毫秒级响应。