Skip to content

Elasticsearch 核心概念

Elasticsearch 的核心思想可以概括成一句话:

写入时把业务文档加工成可搜索结构,查询时不扫全量数据,而是通过倒排索引快速找到候选结果。

零基础学习 ES,要先把几个概念吃透:文档、字段、Mapping、倒排索引、分片、副本、refresh、segment。否则后面看到 matchtermboolagg 时很容易只会写语法,不知道为什么。

为什么数据库 LIKE 不够

假设商品表有几百万条数据,用户搜索:

sql
where product_name like '%无线降噪耳机%'

会遇到几个问题:

  1. 前后都有 % 时,普通 BTree 索引很难发挥作用。
  2. 中文没有空格,数据库不知道“无线”“降噪”“耳机”哪些是有意义的词。
  3. 数据库只能判断“包含不包含”,很难知道哪个商品更相关。
  4. 高亮、同义词、搜索建议、聚合筛选都要额外开发。

ES 的做法是提前分词并建立倒排索引。搜索时不是逐行扫描商品名,而是根据词直接找到包含这些词的文档。

基本名词

Index

Index 是索引库,可以理解成一类搜索文档的集合。

商业系统常见索引:

  1. product_search:商品搜索。
  2. order_search:后台订单检索。
  3. ticket_search:工单检索。
  4. app_log-2026.07.01:日志索引。

Index 不等于数据库表。数据库表按事务存储设计,ES Index 按搜索场景设计。

Document

Document 是 ES 中的一条 JSON 数据。

例如一件商品可以是一条 document:

json
{
  "id": 1001,
  "productName": "无线蓝牙降噪耳机 Pro",
  "brandName": "SoundMax",
  "price": 29900,
  "stockStatus": "IN_STOCK"
}

这条文档可能由商品表、品牌表、类目表、库存表、销量统计表组合而来。ES 更推荐把搜索需要的字段提前冗余进文档,而不是查询时临时 Join。

Field

Field 是文档里的字段,例如:

  1. productName
  2. brandName
  3. categoryId
  4. price
  5. stockStatus
  6. updatedAt

不同字段的搜索目标不同,所以字段类型也不同。商品名要分词,品牌名要精确过滤,价格要范围查询,时间要排序。

Mapping

Mapping 是字段映射,决定字段:

  1. 用什么类型。
  2. 是否分词。
  3. 是否能排序。
  4. 是否能聚合。
  5. 用哪个 analyzer。

Mapping 设计错了,后期经常不能直接改,需要新建索引并重建数据。

ES 是怎么完成搜索的

mermaid
flowchart TD
    A["原始业务数据"] --> B["组装搜索文档"]
    B --> C["根据 Mapping 处理字段"]
    C --> D["text 字段分词"]
    D --> E["建立倒排索引"]
    F["用户输入关键词"] --> G["查询分词"]
    G --> H["匹配倒排索引"]
    H --> I["结构化条件过滤"]
    I --> J["相关性排序"]
    J --> K["返回结果"]

写入和查询是配套的。写入时怎么分词,查询时就要用兼容的分词方式;字段写入时是什么类型,查询时就要使用匹配的查询语义。

文档写入后的内部结构

ES 中的一条 JSON 文档不会原样用于全文检索。它会被拆成多个字段,每个字段根据 Mapping 决定如何索引。

mermaid
flowchart TD
    A["商品 JSON 文档"] --> B["productName"]
    A --> C["brandName"]
    A --> D["price"]
    A --> E["stockStatus"]
    B --> F["text: 分词"]
    C --> G["keyword: 整体值"]
    D --> H["number: 数值索引"]
    E --> I["keyword: 整体值"]
    F --> J["倒排索引"]
    G --> K["精确值索引"]
    H --> L["范围和排序结构"]
    I --> K

例如:

json
{
  "productName": "无线蓝牙降噪耳机 Pro",
  "stockStatus": "IN_STOCK"
}

productName 可能会被拆成:

text
无线 -> doc1001
蓝牙 -> doc1001
降噪 -> doc1001
耳机 -> doc1001
pro -> doc1001

stockStatus 通常作为整体值写入:

text
IN_STOCK -> doc1001

这就是为什么 productName 适合 matchstockStatus 适合 term

什么是倒排索引

倒排索引是理解 ES 的关键。

正排思路

先看文档,再看文档里有哪些词。

text
doc1001 -> 无线, 蓝牙, 降噪, 耳机
doc1002 -> 有线, 游戏, 耳机
doc1003 -> 蓝牙, 音箱

倒排思路

先看词,再看这个词出现在哪些文档里。

text
蓝牙 -> doc1001, doc1003
耳机 -> doc1001, doc1002
降噪 -> doc1001

搜索“蓝牙耳机”时,ES 可以快速找到 蓝牙耳机 对应的文档集合,再计算哪些文档更相关。

倒排索引里通常存什么

倒排索引不只是“词 -> 文档 ID”。为了支持相关性排序、短语查询和高亮,它还可能保存更多信息:

信息用途
term被索引的词
document id哪些文档包含这个词
term frequency词在文档中出现几次
position词在文本中的位置,支持短语查询
offset词在原文中的字符位置,支持高亮

这也是为什么 ES 比普通 like 更适合全文搜索:它不是临时扫描字符串,而是提前为搜索准备了结构化索引。

Segment、Refresh、Flush、Merge

ES 是近实时搜索,不是每次写入都马上变成永久索引文件。

mermaid
flowchart TD
    A["写入文档"] --> B["内存缓冲区"]
    A --> C["translog 事务日志"]
    B --> D["refresh"]
    D --> E["生成可搜索 segment"]
    E --> F["多个小 segment"]
    F --> G["merge 合并成较大 segment"]
    C --> H["flush 持久化并截断 translog"]
概念作用初学者怎么理解
refresh把内存中的数据变成可搜索 segment写入后多久能搜到
segmentLucene 的不可变索引文件搜索真正读取的文件单元
translog写入日志,用于故障恢复防止还没刷盘就宕机导致数据丢失
flush持久化并清理旧 translog做一次更完整的落盘整理
merge合并多个小 segment减少小文件,提高查询效率

为什么说 segment 不可变:已经写入的 Lucene 段文件不会原地修改。更新文档时,本质上是标记旧文档删除,再写入新文档。删除也不是马上物理删除,而是先打删除标记,后续 merge 时再真正清理。

这解释了几个线上现象:

现象原因
写入后短时间搜不到refresh 还没发生
更新文档也会增加磁盘压力更新是删除旧文档再写新文档
大量小批量写入影响性能频繁 refresh 产生很多小 segment
删除后磁盘不立刻下降需要等待 merge
写入高峰查询变慢merge、refresh、索引写入都会消耗资源

分词为什么重要

用户搜索的通常不是数据库中的完整字段,而是自然语言片段。

例如商品名:

text
Apple iPhone 15 Pro Max 256G 官方旗舰

用户可能搜索:

text
苹果手机 15pro 256

如果没有分词、大小写归一、同义词和型号词处理,系统可能搜不到或排序很差。

ES 更像搜索引擎,不是关系型数据库替代品

ES 适合:

  1. 搜索。
  2. 聚合分析。
  3. 海量文本检索。
  4. 近实时日志查询。

ES 不适合直接替代事务型数据库。

原因解释
事务能力不同ES 不提供关系型数据库那种多行强事务
更新成本不同ES 更新文档是删除旧版本再写新版本
Join 能力有限ES 更推荐反范式文档模型
近实时特性写入后受 refresh 影响,不一定立即可搜索
建模目标不同ES 为搜索和分析优化,不为复杂事务建模优化

正确做法是让数据库作为事实来源,ES 作为搜索副本。这样即使 ES 数据异常,也可以从数据库重建。

数据为什么常常要同步到 ES

真实数据通常先存 MySQL,再同步一份到 ES 建立搜索索引。

mermaid
flowchart TD
    A["业务操作<br/>改价、上下架、创建订单"] --> B["写入 MySQL"]
    B --> C["发送 MQ 或写 Binlog"]
    C --> D["同步服务消费变更"]
    D --> E["组装 ES 文档"]
    E --> F["写入 Elasticsearch"]
    G["用户搜索"] --> F

如果直接把 ES 当主库,会把交易一致性、搜索性能、索引变更、数据恢复混在一起,系统会非常难维护。

商业系统常见字段

商品搜索:

  1. id
  2. productName
  3. subTitle
  4. brandId
  5. brandName
  6. categoryId
  7. categoryName
  8. tags
  9. price
  10. stockStatus
  11. saleCount
  12. updatedAt

订单检索:

  1. orderId
  2. buyerId
  3. buyerMobileSuffix
  4. productName
  5. orderStatus
  6. payAmount
  7. createdAt

日志检索:

  1. traceId
  2. service
  3. level
  4. path
  5. message
  6. costMs
  7. @timestamp

常用 REST API Demo

查看集群状态:

json
GET /_cluster/health?pretty

查看索引:

json
GET /_cat/indices?v

查看 Mapping:

json
GET /product_search/_mapping

查看单条文档:

json
GET /product_search/_doc/1001

按 ID 批量查询:

json
GET /product_search/_mget
{
  "ids": ["1001", "1002", "1003"]
}

批量写入:

json
POST /_bulk
{ "index": { "_index": "product_search", "_id": "1001" } }
{ "id": 1001, "productName": "无线蓝牙耳机", "stockStatus": "IN_STOCK" }
{ "index": { "_index": "product_search", "_id": "1002" } }
{ "id": 1002, "productName": "有线游戏耳机", "stockStatus": "IN_STOCK" }

Bulk 的每一行都是一段 JSON,最后也要保留换行。批量写入适合初始化索引、同步历史数据和高吞吐写入。

本章小结

Elasticsearch 的核心思路是:

把文本提前拆词并建立倒排索引,让搜索时不再扫整库,而是直接根据词快速定位结果。

理解了文档模型、倒排索引、Mapping、分词、refresh 和 segment,后面的 Query DSL、聚合、搜索实战和线上排查就能真正串起来。