Elasticsearch 入门扫盲:ES 到底是什么、怎样工作,以及「倒排索引」为什么快?

wxh Lv3

Elasticsearch它究竟是一套什么软件?为什么它的 Index 居然能类比 MySQL 的表?一个索引拆成多个 Shard 之后,到底是谁在搜索?尤其是「倒排索引」这个名字,听起来像某种神奇算法,好像把索引倒过来,搜索就突然变快了。

这篇文章不假设读者熟悉搜索引擎。我们从一份病历的搜索需求出发,先认识软件与进程,再沿着一次数据写入和查询的实际路线,亲手构造一份倒排索引,最后用几条可以执行的请求验证它的工作方式。

一、为什么需要 Elasticsearch?先从一个搜索框说起

假设我们在开发医疗科研平台,已经积累了数百万份电子病历。医生想搜索:

「找出病历中同时提到胸痛和呼吸困难的患者。」

如果病历存放在 MySQL 中,一个直观的写法是:

1
2
3
4
SELECT record_id, patient_id, content
FROM medical_record
WHERE content LIKE '%胸痛%'
AND content LIKE '%呼吸困难%';

数据少的时候,这样做没有什么问题。但是随着病历增长,新的困难会陆续出现:

  1. LIKE '%词语%' 通常难以高效利用普通 B+Tree 索引,可能需要检查大量文本。
  2. 搜索要求越来越丰富:支持多个词、短语、错别字、相关性排序、高亮显示等。
  3. 数据分散在多台机器,如何并行检索并把结果合并?
  4. 除了正文,还想按照医院、年龄、日期等字段过滤结果。

这些需求催生了专门的搜索引擎。Elasticsearch(简称 ES)就是其中的代表。

它是一款分布式搜索与分析引擎,核心全文检索能力建立在 Apache Lucene 上。今天的 ES 还支持聚合分析、地理位置查询和向量搜索等功能,但最容易帮助我们理解它的切入点,仍然是全文搜索。

先记住一个重要区别:

  • MySQL 首先解决业务记录的存储、事务和查询问题;可以搜索,也有 FULLTEXT 等能力。
  • Elasticsearch 把「如何快速找到匹配的文档」作为核心设计目标;它不意味着能替代业务数据库的事务和关联语义。

换句话说,ES 不是给 MySQL 加速的魔法插件,而是一套需要部署、存储数据、建立索引并维护的独立软件。

二、先把几个容易混淆的名字认清楚

刚入门时,最头疼的不是技术难,而是同一个英文词在不同体系里含义不一样。

ES 术语 先这样理解 对照 MySQL 时要注意什么
Cluster(集群) 多个 ES 节点组成的一套服务 不等于一个 MySQL Database
Node(节点) 一个正在运行的 ES 服务实例 更接近一个服务进程,而非一张表
Index(索引) 一组可搜索文档的逻辑集合 大致类似 Table,不是 MySQL 的二级索引
Document(文档) 一条通常以 JSON 表示的记录 大致类似 Row
Field(字段) 文档中的一个属性 大致类似 Column
Mapping(映射) 字段类型与索引方式的定义 类似表结构,但语义不完全一样
Shard(分片) 一部分文档的独立搜索单元 不是普通的文件切片
Replica(副本) 主分片的一份复制 用于容错,也可以分担读请求

2.1 为什么 Index 会被类比成 Table?

在 MySQL 中,我们习惯这样思考:

1
2
3
4
5
Database: hospital
└── Table: medical_record
├── Row 101
├── Row 102
└── Index: idx_patient_id ← 给表建立的辅助索引

但在 Elasticsearch 中,是这样组织数据的:

1
2
3
4
5
6
Elasticsearch Cluster
└── Index: medical_records ← 一组可搜索的文档
├── Document 101
├── Document 102
├── Mapping(字段结构)
└── Shards(实际承载数据的分片)

所以,ES Index 与 MySQL Table 的类比,来自「它们都组织一组记录」这一点,而不是底层数据结构相同。

为什么偏偏叫 Index,不叫 Table?因为 ES 继承了搜索引擎 Lucene 的术语。在 Lucene 的世界里,Index 原本就指一套已经组织好、可以检索的文档及其索引结构。ES 在这层能力上又增加了网络接口、集群管理、分片和复制。

这不是谁用错了「索引」一词,只是关系型数据库和搜索引擎的关注点不同:前者从「表」出发,后者从「可检索的文档集合」出发。

2.2 Document 是不是只能存大段文字?

不是。一个 ES Document 通常是 JSON 对象:

1
2
3
4
5
6
7
{
"record_id": "R101",
"patient_id": "P001",
"age": 56,
"visit_date": "2026-10-01",
"content": "患者反复胸痛,伴有呼吸困难"
}

它既有结构化字段,也有非结构化文本。ES 的 Mapping 决定这些字段如何处理,例如:

  • keyword:把患者 ID、病历 ID 等作为完整词项,用于精确匹配。
  • integer:保存整数,可以做数值范围过滤。
  • date:日期字段,可以筛选时间范围。
  • text:用于全文搜索,通常先经过文本分析(其中包括分词)再建立索引。

Document 是 JSON,不代表 ES 对所有字段都采用同一种搜索方式。 这句话后面会很重要。

三、ES 到底是什么软件?启动后长什么样?

Elasticsearch 是一套主要基于 Java 构建的服务端软件。我们可以把它部署在服务器、虚拟机、容器或云服务上。

启动一个 Elasticsearch 实例,就得到了一个 Node;若干互相连接的 Node 就组成 Cluster。只有一个 Node 时,也可以构成单节点集群。

与 HDFS 那种 NameNode、DataNode 分别承担特定职责的架构相比,ES 通常启动的是同一种服务程序,然后由配置决定不同节点扮演什么角色。

3.1 初学者首先认识这四类职责

角色或职责 做什么 可以怎么理解
Master-eligible / Elected Master 有资格参加主节点选举;被选出的 Master 管理集群状态、成员与分片分配 集群管理员
Data Node 保存 Shard,负责实际读写、搜索和聚合 真正存数据、干搜索的机器
Ingest Node 可在写入前运行数据加工流水线 入库前的加工站
Coordinating(协调职责) 接收请求、转发到相关分片、汇总结果 本次请求的调度员

这里有两个细节需要纠正初学者最容易产生的误解:

第一,Master-eligible 不等于 Master。 多个节点可以具有参选资格,但正常运行时只有一个被选为 Master。它主要管理集群,不负责亲自处理每条搜索请求。

第二,Coordinating 不是必须独立部署的一种特殊进程。 任何 ES 节点都可以协调请求;只有当我们专门将 node.roles 配置为空数组时,它才成为只承担协调职责的节点。

随着集群规模变大,还可以按冷热数据、机器学习等需求细分角色。不过,理解基础搜索原理,先认识上面四类职责就够了。

3.2 一个三节点集群的示意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
      Java 业务服务 / Kibana
|
HTTP 请求
v
+----------------+
| 协调节点(职责)|
+----------------+
|
分发到相关 Shard
/ | \
v v v
+---------+ +---------+ +---------+
| Node A | | Node B | | Node C |
| Data | | Data | | Data |
| Shard 0 | | Shard 1 | | Shard 2 |
+---------+ +---------+ +---------+
\ | /
\ 局部查询结果 /
v v v
协调节点
合并、排序、返回

图中为方便说明省略了副本与其他角色。实际的请求协调可以由 Node A、B、C 其中一个节点兼任,不一定需要额外服务器。

注意:一个集群并不是每台机器都保存全部数据。 这正是 Shard 要解决的问题。

四、Index 为什么要拆成 Shard?

假设 medical_records 这个 Index 有 900 万份病历。

如果每个节点都存全部数据,那么增加机器只是不断复制完整数据,并不能真正分摊存储负担。

ES 可以把一个 Index 拆成若干 Primary Shard(主分片):

1
2
3
4
5
6
7
8
9
10
11
12
Index: medical_records(900 万份病历)
|
按路由分布
|
+----+----+----+
| | |
v v v
Shard 0 Shard 1 Shard 2
一部分 一部分 一部分
文档 文档 文档
| | |
Node A Node B Node C

这里的「分成三份」是逻辑示意,不保证每个分片恰好 300 万条。

一个 Shard 本身就是一个独立的 Lucene Index,它可以保存文档,也可以自行执行查询。进一步往里看,Lucene 会使用 Segment(段)等结构组织索引数据。后面讲的倒排索引,就存在于这个更底层的搜索结构中。

4.1 搜索会在每个 Shard 上发生吗?

通常是:协调节点把搜索请求发给目标索引的相关逻辑分片,各分片分别检索自己的数据,然后再合并结果。

不过不能理解为「永远搜索全部分片」:按文档 ID 读取、指定 Routing、某些可裁剪的查询等,可能缩小要访问的分片范围。

4.2 Primary 和 Replica 是什么关系?

1
2
3
4
5
Node A:Shard 0 Primary
|
| 复制写入操作
v
Node B:Shard 0 Replica
  • Primary 是该分片写入操作的主要入口,负责协调副本复制。
  • Replica 保存相同数据的副本,能够提高容错能力,并分担搜索读取。
  • 对普通搜索,通常从同一逻辑分片的某个可用副本中选择一份执行,不需要主、副本重复搜索一遍相同数据。

分片越多不代表一定越快。分片会带来内存、调度、合并等额外开销,分片数量应该根据数据规模、机器资源与查询特征设计。

现在的问题是:即使分片把 900 万份病历拆成三份,一个分片仍可能包含几百万份病历。它怎么在内部找到「胸痛」?

答案才是全文的主角:倒排索引。

五、倒排索引到底「倒排」了什么?

先给一个完全不神秘的定义:

倒排索引(Inverted Index)不是把词反着写,也不是把文件倒序存放,而是把「文档 → 词」的关系预先整理为「词 → 文档列表」。

它最像一本书最后的关键词索引页:

1
2
胸痛:第 3、8、21 页
呼吸困难:第 8、21、37 页

你要找「胸痛」,不会从书的第一页开始逐字读,而是先翻索引页,直接找到相关位置。

搜索引擎做的事情类似,只不过它处理的是大量文档,而不是一本书的页码。

5.1 假设我们只有四份病历

为了把原理看清楚,暂时不用真实的复杂病历,只看已经整理好的词项:

文档 ID 病历内容(简化)
101 胸痛、呼吸困难
102 胸痛、发热
103 呼吸困难、咳嗽
104 胸痛、呼吸困难、咳嗽

现在要查询:同时包含「胸痛」和「呼吸困难」的文档。

不使用搜索索引时,最直观的方法是挨个查看:

1
2
3
4
检查 101 → 命中
检查 102 → 不命中
检查 103 → 不命中
检查 104 → 命中

四份文档没有压力,但对于一千万份病历,这种做法就可能非常昂贵。

5.2 第一步:整理「文档 → 词项」关系

1
2
3
4
101 → 胸痛、呼吸困难
102 → 胸痛、发热
103 → 呼吸困难、咳嗽
104 → 胸痛、呼吸困难、咳嗽

这是一种很自然的正向关系:我知道文档 ID,想知道里面有哪些词。

5.3 第二步:把关系「反过来」

遍历全部文档,把相同词语聚集起来:

1
2
3
4
胸痛      → [101, 102, 104]
呼吸困难 → [101, 103, 104]
发热 → [102]
咳嗽 → [103, 104]

这,就是倒排索引最核心的样子。

为什么叫「倒排」?因为它把原本的查找方向反了过来:

1
2
原来:文档 ID  → 词语
现在:词语 → 文档 ID

每个词后面的文档 ID 集合,叫做 Posting List(倒排列表)。而所有可检索的词,构成 Term Dictionary(词典)。

可以把它理解成两个步骤:先在词典中找到词,再拿到这个词对应的文档清单。

5.4 第三步:AND 查询为什么突然简单了?

查找「胸痛 AND 呼吸困难」时,只要读取两份列表:

1
2
3
4
5
6
胸痛      → [101, 102, 104]
呼吸困难 → [101, 103, 104]
|
求交集
v
[101, 104]

结果是 101 和 104。

如果查询是「胸痛 OR 呼吸困难」,那就改成求并集:

1
2
3
4
5
6
胸痛      → [101, 102, 104]
呼吸困难 → [101, 103, 104]
|
求并集
v
[101, 102, 103, 104]

看到了吗?我们没有重新阅读病历正文,只是在已经整理好的文档 ID 列表之间做集合运算。

这就是倒排索引让人觉得「像魔法」的地方。

5.5 一个真正值得理解的小细节:列表通常是有序的

为什么有序很重要?

两个排好序的列表,可以利用双指针思路寻找交集。拿上面的例子:

1
2
3
4
5
6
7
A:101 → 102 → 104
B:101 → 103 → 104

101 == 101:记录 101,两边都前进
102 < 103:A 前进
104 > 103:B 前进
104 == 104:记录 104

过程结束,找到 [101, 104]。

这只是直观说明。真实 Lucene 还会使用压缩、跳跃、块级信息等优化,不是只能采用这种最基础的算法。

5.6 「倒排」并不等于「所有查询复杂度都变成 O(1)」

这里必须打破另一个误会。

假设有一千万份病历,其中 999 万份都含有「患者」这个词:

1
患者 → [1, 2, 3, 4, ... 9,990,000]

这个词对应的倒排列表仍然很长。它可能几乎过滤不掉任何文档。

所以倒排索引为什么快,关键不在于名字里有「倒」字,而在于:

  1. 提前付出建索引成本,不必查询时从头分析所有文本。
  2. 根据词项迅速定位候选文档,过滤掉大量不相关数据。
  3. 利用有序、压缩的倒排列表进行高效运算。
  4. 结合评分、缓存、跳跃和其他优化,减少无用工作。

搜索非常常见的词、做复杂通配符匹配、返回海量结果或深度分页时,仍然可能很慢。

倒排索引不是凭空消灭计算成本,而是把部分计算和存储成本从查询阶段提前到了写入阶段。

六、分词是倒排索引的前提:中文尤其容易踩坑

前面的演示,默认计算机已经知道「胸痛」「呼吸困难」是两个词。

但计算机收到的原始文本其实是:

1
患者反复胸痛,伴有呼吸困难

它怎么知道该拆成哪些词?这就需要 Analyzer(分析器)。

通常可以把文本分析粗略理解为:

1
2
3
4
5
6
7
8
9
原始文本
|
字符预处理(例如清理某些格式)
|
Tokenizer:切分词项
|
Token Filter:大小写归一化、停用词等处理
|
得到可以建立索引的 Term

注意:中文词边界不像英文那样普遍有空格。 ES 的默认 standard 分词器未必符合医疗场景希望得到的中文医学词汇边界。

例如「心肌梗死」「呼吸困难」「糖化血红蛋白」是否被正确识别,就会影响搜索召回率和准确率。真实系统需要测试并选择适合中文和专业术语的分析方案,必要时引入医学词典、同义词体系或定制分析器。

另外,写入时怎么分词,查询时也要采取兼容的分析方式。否则我们建好了一份词项词典,查询却用另一套无法对应的词项,自然查不出来。

6.1 text 与 keyword 的区别

假设同样的文字是 胸痛 呼吸困难:

  • text 字段可以经过分词,形成「胸痛」「呼吸困难」这样的词项,适合全文搜索。
  • keyword 通常把整个值当作一个词项,适合患者 ID、编码、状态、标签等精确匹配。

因此:不是 ES 中任何一个字符串字段都应该分词。 把患者 ID 错误地当成全文文本处理,和把一整份病历当作一个不可拆分的关键词,都会让检索设计变得别扭。

七、倒排索引还会保存什么?为什么能按相关性排序?

我们手工制作的索引,只保存了「词 → 文档 ID」。真实 Lucene 可以保存更多信息,例如:

  • 词频(Term Frequency):一个词在某份文档里出现了几次。
  • 词项位置(Position):词出现在文档的哪个位置,可以帮助短语或邻近搜索。
  • 词项偏移(Offset):词在原文里的起止位置,可以辅助文本高亮等功能。
  • 文档统计信息:帮助评估某个词是常见词还是稀有词。

这些信息并不保证每个字段都全部保存,具体取决于 Mapping 与索引选项。

例如用户搜索「胸痛」,两份文档都命中了,但其中一份只有一句话随便提及,另一份详细描述了多次胸痛事件。ES 可以使用 BM25 等相关性评分机制,考虑词频、词的稀有程度、字段长度等因素,给文档计算分数。

所以 ES 不仅回答「匹配没匹配」,还可以回答「哪些结果更值得排在前面」。

但是,相关性分数并不等于医学诊断价值。文本检索的高分只是搜索算法意义上的更相关。

八、一次写入请求,ES 内部经历了什么?

现在把刚才认识的 Node、Index、Shard、倒排索引连接起来。

假设 Java 服务提交一份病历:

1
2
3
4
5
{
"record_id": "R101",
"patient_id": "P001",
"content": "胸痛 呼吸困难"
}

大致过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Java 服务提交 JSON
|
v
某个 ES Node 接收请求
|
| (如配置了 Ingest Pipeline,可先执行加工)
v
确定目标 Index 和 Primary Shard
|
v
主分片接收写入,Lucene 处理字段并建立索引
|
+----> 同步相关副本写入操作
|
v
返回写入结果
|
v
经过 Refresh 后,普通搜索可以看见新文档

这个流程有两个重要细节:

第一,写入不是向每一个主分片广播同一份文档。 ES 根据路由规则,把文档归属到某个主分片,再由主分片协调其副本复制。

第二,写入成功与「立刻能被搜索」不是完全相同的事情。

ES 是近实时(Near Real-Time)搜索引擎。新写入的文档需要经过 Refresh,相关的索引段对搜索可见后,普通搜索才能看到它。Refresh 与持久化提交也不是同一个概念。

如果业务场景需要「刚写完就查询到」,可以了解写入 API 的 refresh=wait_for 参数;它等待相关数据变为可搜索,而不是每次强行立刻刷新。

九、一次搜索请求又经历了什么?

假设查询:胸痛 AND 呼吸困难。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Java 服务发起 _search
|
v
协调节点解析请求,确定目标 Index / Shard
|
v
请求分发到各相关逻辑分片的一份可用副本
|
+--> Shard 0:分析查询词、查倒排列表、求交集、评分
|
+--> Shard 1:分析查询词、查倒排列表、求交集、评分
|
+--> Shard 2:分析查询词、查倒排列表、求交集、评分
|
v
协调节点汇总各分片候选结果,形成全局排序
|
v
取得需要返回的文档内容(fetch)
|
v
返回 JSON 搜索结果

这是一种便于理解的简化描述。ES 的搜索通常涉及查询与获取结果等阶段,实际执行还会受到查询类型、路由、缓存和优化策略等影响。

最重要的是区分两种不同层次的加速:

  • Shard:把数据分散到不同节点,解决存储规模和并行执行问题。
  • 倒排索引:让单个 Shard 内部不必为了找一个关键词而逐篇扫描全文。

只有弄清这两层,才算真正把 ES 的「分布式」和「搜索」连接起来。

十、动手试一遍:不用写 Java,也能理解 ES 怎么用

下面这组请求使用 Kibana → Dev Tools → Console 的语法。只要你已经有一套可访问的 Elasticsearch 环境,就可以逐条执行。

没有环境的话,可以先参考官方的本地部署教程。为避免不同 ES 版本、认证方式和 Docker 参数混用,本文不硬编码一套可能过时的安装命令。

教学约定:我们把中文词语手工用空格隔开,再使用 ES 内置 whitespace 分词器。这样不用额外安装中文分词插件,就可以稳定复现本文的倒排索引示例。这只适合教学,不是中文真实病历的推荐配置。

10.1 创建一个 Index

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PUT /medical_records_demo
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"patient_id": { "type": "keyword" },
"content": {
"type": "text",
"analyzer": "whitespace"
}
}
}
}

这里的意思是:

  • 创建一个叫 medical_records_demo 的文档集合。
  • 只配置 1 个主分片、0 个副本,方便在本地单节点学习;生产环境不要直接照搬无副本配置。
  • patient_id 使用精确匹配的 keyword。
  • content 使用全文搜索的 text,并以空格切词。

10.2 插入四份文档

1
2
3
4
5
PUT /medical_records_demo/_doc/101?refresh=wait_for
{
"patient_id": "P001",
"content": "胸痛 呼吸困难"
}
1
2
3
4
5
PUT /medical_records_demo/_doc/102?refresh=wait_for
{
"patient_id": "P002",
"content": "胸痛 发热"
}
1
2
3
4
5
PUT /medical_records_demo/_doc/103?refresh=wait_for
{
"patient_id": "P003",
"content": "呼吸困难 咳嗽"
}
1
2
3
4
5
PUT /medical_records_demo/_doc/104?refresh=wait_for
{
"patient_id": "P004",
"content": "胸痛 呼吸困难 咳嗽"
}

这里的 _doc/101 指定了文档 _id 为 101;refresh=wait_for 保证请求返回前相关写入已可被普通搜索看到,便于我们立即验证。

10.3 验证分词结果

1
2
3
4
5
POST /medical_records_demo/_analyze
{
"field": "content",
"text": "胸痛 呼吸困难"
}

你应该能在 tokens 中看到「胸痛」和「呼吸困难」两个词项。

10.4 搜索「胸痛 AND 呼吸困难」

1
2
3
4
5
6
7
8
9
10
11
GET /medical_records_demo/_search
{
"query": {
"match": {
"content": {
"query": "胸痛 呼吸困难",
"operator": "and"
}
}
}
}

这里指定 operator: "and" 很重要:match 查询在分析后,默认的词项组合行为通常是 OR,如果不明确指定,很可能查出不符合本例预期的结果。

预期命中的文档 _id 是 101 和 104。结果里还会出现 _score 等字段,这是相关性评分的一部分。

10.5 改成 OR,再看看有什么不同

把上面的 "and" 改成 "or":

1
2
3
4
5
6
7
8
9
10
11
GET /medical_records_demo/_search
{
"query": {
"match": {
"content": {
"query": "胸痛 呼吸困难",
"operator": "or"
}
}
}
}

此时四份文档都会命中,只是排序、评分可能不同。

这就是你刚才手工求交集、求并集的原理在真实搜索 API 中的一次验证。

10.6 精确查询患者 ID

1
2
3
4
5
6
7
8
GET /medical_records_demo/_search
{
"query": {
"term": {
"patient_id": "P001"
}
}
}

这里使用 term 精确匹配 keyword 字段,而不是对患者 ID 做全文分词。

10.7 看看索引和分片

1
2
GET /_cat/indices/medical_records_demo?v
GET /_cat/shards/medical_records_demo?v

前一个命令能看到 Index 的一些基本状态,后一个可以看到分片信息。未来如果你把数据部署到多节点集群,这两条命令会更加直观。

这些 API 都需要相应的访问权限;具体认证方式以你使用的本地或云部署环境为准。教学结束后可以使用 DELETE /medical_records_demo 删除测试索引,但不要在生产环境随意执行删除命令。

十一、ES、MySQL、Doris 到底怎么分工?

学到这里,很容易产生一个新问题:既然 ES 搜索这么快,是不是以后直接用 ES 存全部业务数据?

答案通常是:不要因为一种技术擅长某种查询,就要求它承担所有职责。

需求 通常优先考虑
用户、订单、患者主数据的事务写入与强业务约束 MySQL、PostgreSQL 等事务型数据库
大规模病历正文检索、关键词匹配、搜索相关性排序 Elasticsearch
大量结构化事实的分组统计、复杂分析 SQL、宽范围聚合 Doris、ClickHouse 等 OLAP 引擎
日志的集中搜索、排障、事件分析 Elasticsearch 及周边采集、可视化工具
语义相似检索、向量检索 ES 也可参与,但需要评估模型、召回效果与成本

比如医疗科研平台可以做这样的职责划分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
医院原始数据
|
v
清洗、归一化、患者与就诊关系校验
|
+------> MySQL:业务数据与处理状态
|
+------> Doris:结构化医疗事实分析
|
+------> ES:病历正文与文书全文检索
|
搜索结果返回患者 ID、文书 ID
|
业务层按查询语义进行关联

如果查询是「患者年龄大于 50 岁,并且某项检验指标超过阈值」,结构化数据库或分析引擎就可能更合适。

如果查询是「病程记录提到持续胸痛、呼吸困难」,ES 的全文搜索能力则值得优先考虑。

如果要求两个条件属于同一次就诊,业务层就必须明确患者 ID、就诊 ID、文书 ID 之间的关系。ES 的倒排索引能找到文本,不会替我们判断临床语义。

同样要考虑:数据同步延迟、索引重建、权限控制、脱敏审计,以及 ES 与主业务库的一致性边界。

十二、几个常见误区,一次说明白

误区一:「ES 的 Index 就是 MySQL Index。」

不是。ES Index 是可搜索的文档集合;MySQL Index 通常是依附于表的辅助数据结构。两个体系的 Index 处于不同抽象层次。

误区二:「Shard 就是一段切碎的 JSON 文件。」

不是。Shard 是具备独立索引与搜索能力的 Lucene 索引,内部还有 Segment 等结构。

误区三:「倒排索引是把文字倒着排列,所以搜索快。」

不是。它改变的是查找关系的方向:从「文档有哪些词」变成「词出现在哪些文档」。

误区四:「ES 每次搜索都要交给 Master。」

不是。协调节点负责请求分发与结果合并,数据节点在分片上真正执行搜索;Master 主要负责集群管理。

误区五:「主分片和副本会把同一份数据各搜一次。」

一般不会。一次普通搜索通常为每个相关逻辑分片选择一份可用分片副本进行查询。

误区六:「倒排索引让任何搜索都是瞬间完成。」

也不是。高频词、复杂匹配、海量命中、深度分页及资源不足等情况都会影响性能。

误区七:「写入成功,新文档就一定立刻可被普通搜索查到。」

不一定。ES 有 Refresh 机制,是近实时搜索系统。

误区八:「ES 的全文搜索自动理解同义词和医学概念。」

不是。分词、同义词、医学术语归一化、语义向量检索都需要明确设计和验证。

十三、最后用一张结构图把所有概念串起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
                 Elasticsearch Cluster
|
+-------------------+-------------------+
| | |
Node A Node B Node C
(运行的 ES 实例) (运行的 ES 实例) (运行的 ES 实例)
| | |
Shard 0 Shard 1 Shard 2
(Lucene 索引) (Lucene 索引) (Lucene 索引)
| | |
Segment Segment Segment
| | |
倒排索引 倒排索引 倒排索引
词 → 文档 ID 词 → 文档 ID 词 → 文档 ID
\ | /
\---------- 搜索结果合并 ------------/
|
返回给客户端

图中隐藏了副本、多个 Segment、其他索引结构及可能的多角色节点,只保留最重要的主线。

回顾整篇文章,其实只需要抓住四句话:

  1. Elasticsearch 是一套真正运行在服务器上的分布式搜索软件,底层使用 Lucene。
  2. Index 是可搜索的文档集合,Shard 是组成这个集合的独立搜索单元。
  3. 一个搜索请求可以分发到多个 Shard 并行执行,然后由协调节点合并结果。
  4. 倒排索引把「文档 → 词」预先变成「词 → 文档列表」,于是搜索时可以直接查候选文档,而不必逐篇扫描全部正文。

最后这条最值得记住:

倒排索引没有魔法。它只是提前建立了一本高效的「关键词目录」,把大量原本需要查询时完成的工作,搬到了写入和建索引的阶段。

理解了这点,再去学习 BM25、Segment、Refresh、分布式查询与向量搜索,就不会觉得它们是一堆彼此无关的术语了。


附:官方资料与继续阅读

以下链接可用于核对本文的概念及继续深入学习。技术细节会随着 ES 版本演进,生产实践请优先以对应版本文档为准。

  • 标题: Elasticsearch 入门扫盲:ES 到底是什么、怎样工作,以及「倒排索引」为什么快?
  • 作者: wxh
  • 创建于 : 2026-11-02 00:00:00
  • 更新于 : 2026-11-02 21:58:49
  • 链接: https://blog.private-cloud.site/2026/11/02/Elasticsearch扫盲/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
Elasticsearch 入门扫盲:ES 到底是什么、怎样工作,以及「倒排索引」为什么快?