Skip to content

核心概念与倒排索引

1. 这是什么

很多人第一次学 ElasticSearch(后面简称 ES)时,会先记住几个词:

  • Index
  • Document
  • Field
  • Term
  • Analyzer
  • 倒排索引

但背完之后还是会迷糊:

  • 为什么 ES 查关键词这么快?
  • 为什么改了分词器,查询结果就变了?
  • 为什么 ES 的“索引”跟 MySQL 的索引不是一回事?

这篇就先把这些问题讲透。

如果只用一句话解释倒排索引,可以这么记:

普通数据库更像“按记录找内容”,ES 更像“按词找记录”。

比如你有 3 篇文档:

  1. Java 并发编程入门
  2. Java 网络编程与 Netty
  3. MySQL 索引设计实践

如果用户搜索“Java”,ES 不会一篇篇全文扫描后再判断有没有这个词,而是更像先准备一张表:

  • Java -> 文档1, 文档2
  • 并发 -> 文档1
  • Netty -> 文档2
  • MySQL -> 文档3

这样查“Java”时,直接就能定位到相关文档。

这张“词 -> 文档列表”的结构,就是倒排索引最核心的直觉。


2. 为什么重要

如果不理解倒排索引,后面很多 ES 行为你都会觉得像“黑盒”:

  • 为什么 textkeyword 查询效果不一样
  • 为什么搜索 中国人 时结果和预期不同
  • 为什么有时候搜 running 能命中 run
  • 为什么字段建模不合理会拖慢查询和写入

本章的重要性在于:

  1. 它是全文检索的底层心智模型
  2. 它决定了你怎么设计字段映射
  3. 它直接影响查询精度、召回率和性能

换句话说:

如果不懂倒排索引,你大概率只能“会调 API”,还不会“设计搜索系统”。


3. 先和 MySQL 建立区分

初学者最容易混的地方是:

  • ES 里有 Index
  • MySQL 里也有 index

但这两个“索引”不是一回事。

3.1 MySQL 里的索引更像什么

MySQL 索引更多是:

  • 为某个字段建立加速结构
  • 帮助快速定位行记录
  • 本质上还是围绕“表中的行”组织

3.2 ES 里的 Index 更像什么

ES 的 Index 更接近:

  • 一类文档的逻辑集合
  • 类似数据库里的“库/表”混合概念
  • 里面包含 mapping、settings、documents

所以你可以先粗略理解成:

  • MySQL 的 index:给查表加速的结构
  • ES 的 Index:存放文档的一整个索引库

这是两个层级不同的概念。


4. 核心概念,先用人话讲清楚

4.1 Document:文档

ES 里一条数据通常叫一个 Document

例如一篇博客:

json
{
  "title": "Java 网络编程与 Netty",
  "author": "yanqi",
  "content": "Netty 是一个基于事件驱动的网络编程框架",
  "tags": ["Java", "Netty", "网络编程"]
}

这就是一个文档。

4.2 Field:字段

文档里的每个键值对就是字段,比如:

  • title
  • author
  • content
  • tags

字段类型会影响:

  • 是否分词
  • 是否可聚合
  • 是否适合排序

4.3 Term:词项

Term 可以理解为:

  • 经过分析处理后的最小查询单元

比如文本:

text
Java 网络编程与 Netty

经过分词后,可能变成:

  • java
  • 网络
  • 编程
  • netty

这些被拆出来并进入索引结构的词,就是 term。

4.4 Token:分词后的片段

在很多资料里你会同时看到 tokenterm

可以先这样理解:

  • token:分词器处理过程中的结果
  • term:最终进入倒排索引的词项

入门阶段不用在这两个概念上过度纠缠,但要知道它们都和“文本被切成可搜索单元”有关。

4.5 Analyzer:分词器

Analyzer 是 ES 全文检索里非常关键的角色。

它通常包含三步:

  1. 字符过滤(character filter)
  2. 分词(tokenizer)
  3. token 过滤(token filter)

你可以简单理解成:

Analyzer 决定了一段文本,会被拆成哪些词。

这也就决定了:

  • 能不能搜到
  • 搜到了什么
  • 搜得准不准

5. 倒排索引到底长什么样

假设有三篇文档:

text
文档1:Java 并发编程
文档2:Java 网络编程
文档3:MySQL 索引设计

如果用最朴素的方式建倒排索引,可以得到:

text
Java   -> [1, 2]
并发   -> [1]
编程   -> [1, 2]
网络   -> [2]
MySQL  -> [3]
索引   -> [3]
设计   -> [3]

这张表就叫 倒排表

为什么叫“倒排”?

因为它和“正排”是反过来的:

正排思路

  • 文档1 -> 包含哪些词
  • 文档2 -> 包含哪些词
  • 文档3 -> 包含哪些词

倒排思路

  • 词A -> 出现在哪些文档
  • 词B -> 出现在哪些文档
  • 词C -> 出现在哪些文档

搜索引擎更关心的是:

  • 用户搜了一个词
  • 这个词在哪些文档里出现

所以倒排索引特别适合关键词检索。


6. 一个通俗例子:为什么 ES 查得快

把倒排索引想成图书馆索引卡片:

  • 普通扫描:一本本翻书找“分布式”这个词
  • 倒排索引:先去索引卡片里查“分布式”,直接拿到书目编号

这就是 ES 快的核心原因之一。

当然,真实 ES 远比这个复杂,还会涉及:

  • 词频
  • 位置
  • 相关性评分
  • segment
  • merge

但在入门阶段,先建立上面的直觉最重要。


7. textkeyword 到底有什么区别

这是最常见、也最容易踩坑的点。

text

适合:

  • 全文检索
  • 需要分词的文本

例如:

  • 文章标题
  • 正文内容
  • 商品描述

keyword

适合:

  • 精确匹配
  • 聚合
  • 排序
  • 过滤

例如:

  • 用户 ID
  • 订单状态
  • 标签编码
  • 国家代码

一个典型误区

把所有字符串都建成 text

后果是:

  • 你明明想做精确过滤
  • 却变成了分词查询
  • 聚合和排序效果也不理想

一个更常见的设计是多字段:

json
"title": {
  "type": "text",
  "fields": {
    "keyword": {
      "type": "keyword"
    }
  }
}

这样:

  • title 用来全文检索
  • title.keyword 用来精确匹配、排序、聚合

8. 动手做一个最小实验

下面直接用 curl 走一遍,你会比只看概念快得多。

假设本地 ES 运行在 localhost:9200

8.1 创建索引

bash
curl -X PUT 'http://localhost:9200/blog_demo' \
  -H 'Content-Type: application/json' \
  -d '{
    "mappings": {
      "properties": {
        "title": { "type": "text" },
        "author": { "type": "keyword" },
        "content": { "type": "text" }
      }
    }
  }'

8.2 写入两篇文档

bash
curl -X POST 'http://localhost:9200/blog_demo/_doc/1' \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Java 网络编程与 Netty",
    "author": "yanqi",
    "content": "Netty 是一个基于事件驱动的高性能网络框架"
  }'
bash
curl -X POST 'http://localhost:9200/blog_demo/_doc/2' \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "MySQL 索引设计实践",
    "author": "yanqi",
    "content": "索引设计会直接影响查询性能和写入成本"
  }'

8.3 搜索 Netty

bash
curl -X GET 'http://localhost:9200/blog_demo/_search' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": {
      "match": {
        "content": "Netty"
      }
    }
  }'

你应该看到第一篇文档被命中。

8.4 精确过滤作者

bash
curl -X GET 'http://localhost:9200/blog_demo/_search' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": {
      "term": {
        "author": "yanqi"
      }
    }
  }'

这里要特别感受:

  • match 更偏全文检索
  • term 更偏精确匹配

9. 再做一步:看看分词结果

理解 ES,最实用的一步就是学会看 analyzer 的输出。

bash
curl -X POST 'http://localhost:9200/_analyze' \
  -H 'Content-Type: application/json' \
  -d '{
    "analyzer": "standard",
    "text": "Java Netty 入门实践"
  }'

你会得到类似:

json
{
  "tokens": [
    { "token": "java" },
    { "token": "netty" },
    { "token": "入门实践" }
  ]
}

这一步非常关键,因为很多“为什么搜不到”的问题,本质上就是:

  • 你以为分成 A、B、C
  • 实际 analyzer 分出来的是 X、Y、Z

所以排查 ES 查询问题,第一反应常常应该是:

先看 mapping,再看 analyzer 输出。


10. 相关性为什么不是“只要包含就一样”

很多人会问:

  • 都包含关键词,为什么排序不一样?

因为 ES 不只是判断“有没有”,还会计算“相关程度”。

影响相关性的常见因素包括:

  • 词出现次数
  • 字段权重
  • 是否完整匹配
  • 查询语句类型

例如:

  • 标题里有 Netty
  • 正文里也有 Netty

通常标题命中的权重会更高。

这也是为什么搜索系统不是“查到了就结束”,还要管“排得对不对”。


11. 实战里最容易踩的坑

11.1 把 ES 当成关系数据库替代品

ES 很擅长:

  • 搜索
  • 聚合分析
  • 海量文档检索

但它不是为了替代关系数据库的强事务能力而设计的。

11.2 字段类型建错

典型错误:

  • 需要精确匹配的字段用了 text
  • 需要全文检索的字段用了 keyword

结果就是:

  • 搜索不准
  • 聚合不对
  • 排序怪异

11.3 不验证分词结果,只盲目改查询 DSL

很多查询问题不是 DSL 写错,而是:

  • mapping 不合理
  • analyzer 不合理
  • 数据入库方式和查询方式不匹配

11.4 用 termtext 字段

这是新手高频错误。

text 字段通常会先分词后再建立索引,直接 term 查询时,往往和你想象的不一样。


12. 一套排查思路

以后你遇到“为什么搜不到/搜不准”,建议按这个顺序排:

  1. 看字段 mapping
  2. 看字段是 text 还是 keyword
  3. _analyze 看分词结果
  4. 检查查询语句是 matchterm、还是 match_phrase
  5. 看返回结果的 _score
  6. 必要时看 explain/profile

这个顺序比一上来就乱改 DSL 更有效。


13. 练习建议

练习 1:手工建立倒排表

找 3 段短文本,自己手工拆出:

  • 词项
  • 对应文档编号

把“正排”和“倒排”都写一遍。

练习 2:试 textkeyword

创建两个字段:

  • 一个 text
  • 一个 keyword

然后分别用:

  • match
  • term
  • 聚合
  • 排序

观察结果差异。

练习 3:故意制造“搜不到”

把一个字段建成 keyword,然后拿自然语言去 match 它;再把一个字段建成 text,拿 term 去查它。你会更快理解 mapping 和查询类型之间的关系。


14. 自测问题

  • 倒排索引和正排索引的区别是什么?
  • 为什么 ES 更适合全文搜索?
  • textkeyword 的使用场景有什么不同?
  • 为什么 analyzer 会直接影响搜索结果?
  • 为什么排查 ES 查询问题时,经常要先看 _analyze

15. 这一章你至少要带走什么

如果你看完只记住 4 件事,就先记这 4 件:

  1. 倒排索引本质上是“词 -> 文档列表”
  2. ES 的 Index 不是 MySQL 的索引,它更像文档集合
  3. Analyzer 决定文本怎么被拆词,也就决定了怎么搜
  4. 字段类型和查询类型必须匹配,否则很容易搜不准甚至搜不到

把这套心智模型建立起来,后面再学 mapping、DSL、聚合、写入流程,你会轻松很多。