一文看懂 Elasticsearch

从倒排索引到项目实战
前言
在后端开发中,很多业务都会涉及搜索功能,比如商品搜索、文章搜索、日志检索、订单异常排查、用户行为分析等。
如果只是根据主键查一条数据,MySQL 完全够用。但如果需求变成:
用户输入关键词后,要支持模糊搜索、中文分词、条件筛选、排序、高亮、聚合统计,并且数据量还比较大。
这时候继续用 MySQL 的 LIKE '%关键词%' 就会比较吃力。
Elasticsearch,简称 ES,就是专门用来解决搜索和分析问题的分布式搜索引擎。它底层基于 Lucene,核心能力是全文检索、复杂查询和数据聚合分析。
一句话理解:
MySQL 更适合存储准确的业务数据,Elasticsearch 更适合快速搜索和分析数据。
一、Elasticsearch 是什么?
Elasticsearch 是一个分布式搜索和分析引擎,常用于全文检索、日志查询、商品搜索、数据分析等场景。
它可以把一条条业务数据保存成文档,然后通过倒排索引快速查找。
比如一个商品文档可能长这样:
{
"id": 1001,
"name": "Sony 蓝牙降噪耳机",
"brand": "Sony",
"category": "数码耳机",
"price": 899,
"sales": 3200
}
当用户搜索“蓝牙耳机”时,ES 不会像数据库那样从头到尾扫描所有商品,而是通过倒排索引快速找到包含“蓝牙”“耳机”这些词的商品,再根据相关性、销量、价格等条件返回结果。
二、为什么不用 MySQL 做搜索?
MySQL 可以做简单查询,比如:
SELECT * FROM product WHERE name LIKE '%耳机%';
但这种方式在真实业务里会有几个问题。
首先,LIKE '%耳机%' 这种前后模糊匹配很难有效走索引,数据量一大查询就会变慢。
其次,MySQL 对全文检索的支持不如 ES 灵活,尤其是中文搜索。比如用户搜索“蓝牙降噪耳机”,系统应该能理解“蓝牙”“降噪”“耳机”这些关键词,而不是只做简单字符串匹配。
另外,搜索业务通常不只是查关键词,还要支持品牌筛选、价格区间、销量排序、关键词高亮、分类聚合等组合能力。用 MySQL 硬做也能做一部分,但性能和扩展性都不如 ES。
所以在实际项目中,常见架构是:
MySQL:保存核心业务数据
Elasticsearch:提供搜索和分析能力
Redis:缓存热点数据
MQ:异步同步 MySQL 和 ES 数据
三、Elasticsearch 核心概念
1. Document:文档
Document 是 ES 中存储数据的基本单位,类似 MySQL 中的一行记录。
比如一条商品数据、一篇文章、一条日志,都可以作为一个文档。
2. Index:索引
Index 可以理解为一类文档的集合,类似 MySQL 中的表。
例如:
product_index 商品索引
article_index 文章索引
order_log_index 订单日志索引
3. Field:字段
Field 类似 MySQL 中的列。
比如商品文档中有 name、brand、price、category 等字段。
4. Mapping:映射
Mapping 用来定义字段类型和字段是否分词,类似 MySQL 的表结构设计。
常见字段类型有:
keyword:不分词,适合精确匹配,比如订单号、状态、品牌、标签
text:分词,适合全文检索,比如标题、正文、商品描述
integer / long / double:数字类型
date:日期类型
boolean:布尔类型
Mapping 设计很重要,因为它会直接影响查询效果。
比如订单状态 status 应该用 keyword,因为它只需要精确匹配;商品标题 name 应该用 text,因为它需要分词搜索。
四、Elasticsearch 为什么搜索快?
ES 搜索快的核心原因是倒排索引。
普通数据库更像是:
文档 -> 文档里有哪些词
倒排索引则是:
词 -> 这个词出现在哪些文档中
举个例子,有三条商品数据:
商品1:蓝牙降噪耳机
商品2:蓝牙运动耳机
商品3:机械键盘
建立倒排索引后,大概会变成:
蓝牙 -> 商品1、商品2
降噪 -> 商品1
耳机 -> 商品1、商品2
机械 -> 商品3
键盘 -> 商品3
当用户搜索“蓝牙耳机”时,ES 可以直接根据“蓝牙”“耳机”找到相关商品,而不是扫描所有商品记录。
这就是 ES 适合全文搜索的根本原因。
五、分词器是什么?
分词器的作用是把一段文本拆成多个词。
比如:
我想买一副蓝牙降噪耳机
可能会被拆成:
我 / 想 / 买 / 一副 / 蓝牙 / 降噪 / 耳机
英文可以按空格分词,但中文没有天然空格,所以中文搜索通常需要配置中文分词器,比如 IK 分词器。
分词器会直接影响搜索结果。
如果分词太细,搜索结果可能很多,但不够精准;如果分词太粗,又可能搜不到用户想要的内容。
所以在 ES 中,字段设计通常要区分:
需要全文搜索的字段:text
需要精确匹配的字段:keyword
比如商品名称适合 text,订单状态适合 keyword。
六、常见查询方式
1. term 精确查询
term 查询适合查询不分词字段,比如订单状态、订单号、用户 ID、品牌、标签、枚举值等。
这类字段的特点是:查的不是某个关键词,而是一个完整值是否完全相等。
比如订单状态字段 status 可能有这些值:
PAID 已支付
UNPAID 未支付
CANCELLED 已取消
REFUNDED 已退款
如果要查询所有已支付订单,可以这样写:
{
"query": {
"term": {
"status": "PAID"
}
}
}
这段查询的意思是:
找出
status字段值等于PAID的文档。
这里不需要分词,也不需要相关性评分,只需要精确匹配。所以 status 通常会在 Mapping 中设计成 keyword 类型。
{
"mappings": {
"properties": {
"status": {
"type": "keyword"
}
}
}
}
可以简单记住:
term查完整值,通常配合keyword字段使用。
2. match 全文检索
match 查询适合搜索会被分词的文本字段,比如商品标题、文章内容、商品描述等。
例如用户搜索:
蓝牙耳机
ES 会先对这个关键词分词,再去匹配文档中的相关内容。
查询示例:
{
"query": {
"match": {
"name": "蓝牙耳机"
}
}
}
如果商品名称是:
Sony 蓝牙降噪耳机
它就有可能被匹配出来。
match 和 term 最大区别是:
term:精确匹配完整值
match:先分词,再做全文检索
所以搜索商品标题、文章正文时一般用 match;查状态、订单号、分类 ID 时一般用 term。
3. range 范围查询
range 用于查询数字或时间范围。
比如商品搜索中,用户选择价格范围 500 到 1000 元:
{
"query": {
"range": {
"price": {
"gte": 500,
"lte": 1000
}
}
}
}
常见场景包括:
价格区间
时间范围
年龄范围
库存数量
订单金额
4. bool 组合查询
真实项目中最常用的是 bool 查询,因为业务搜索通常不是单个条件,而是多个条件组合。
比如用户搜索商品时,条件可能是:
关键词:蓝牙耳机
品牌:Sony
分类:数码产品
价格:500 到 1000
对应 ES 查询可以这样写:
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "蓝牙耳机"
}
}
],
"filter": [
{
"term": {
"brand": "Sony"
}
},
{
"term": {
"category": "数码产品"
}
},
{
"range": {
"price": {
"gte": 500,
"lte": 1000
}
}
}
]
}
}
}
这里可以这样理解:
must:必须匹配,会影响相关性评分,比如关键词搜索
filter:过滤条件,不参与评分,性能更好,比如品牌、分类、价格
must_not:必须不匹配
should:可选匹配,可以提高相关性分数
实际开发中,关键词通常放在 must,筛选条件通常放在 filter。
七、典型应用场景
1. 商品搜索
商品搜索是 ES 最典型的使用场景。
以电商平台为例,用户输入:
蓝牙降噪耳机
系统要做的不是简单判断商品名称里有没有这几个字,而是要完成一整套搜索流程。
首先,ES 会对用户输入进行分词,比如拆成:
蓝牙 / 降噪 / 耳机
然后在商品名称、商品描述、品牌、分类等字段中查找相关商品。
比如有一条商品数据:
{
"name": "Sony 蓝牙降噪耳机",
"brand": "Sony",
"category": "数码耳机",
"price": 899,
"sales": 3200
}
这条商品就可能因为命中了“蓝牙”“降噪”“耳机”而被搜索出来。
同时,用户可能还会继续筛选:
只看 Sony 品牌
价格在 500 到 1000 元之间
按销量从高到低排序
搜索关键词高亮显示
统计不同品牌下有多少商品
这些能力对应到 ES 中,分别是:
关键词搜索:match
品牌筛选:term
价格筛选:range
销量排序:sort
品牌统计:aggregation
关键词高亮:highlight
所以商品搜索本质上是:
全文检索 + 条件过滤 + 排序 + 聚合统计 + 高亮展示。
这类复杂搜索如果全部用 MySQL 来做,随着商品数量增加,性能和搜索体验都会受到影响。而 ES 的优势就是专门处理这种搜索场景。
2. 日志检索
日志检索也是 ES 很常见的应用。
在分布式系统中,一次请求可能经过多个服务。如果线上出现订单异常,只看单个服务日志很难排查。
如果把日志写入 ES,就可以根据这些条件快速检索:
TraceId
订单号
用户 ID
接口路径
错误级别
时间范围
异常关键字
比如排查订单创建失败,可以根据订单号或 TraceId 搜索整条调用链上的日志,快速定位是库存服务、支付服务还是订单服务出了问题。
这也是很多公司使用 ELK 的原因:
Elasticsearch:存储和检索日志
Logstash / Filebeat:采集日志
Kibana:可视化查看日志
3. 文章和文档搜索
博客、知识库、论坛、帮助中心这类系统,也很适合用 ES。
用户输入关键词后,系统可以从标题、正文、标签中搜索相关内容,并且按照相关性排序。
例如搜索:
Spring Boot Redis 缓存
ES 可以找到标题或正文中包含相关词的文章,并把命中的关键词高亮展示出来。
4. 数据聚合分析
ES 不只能搜索,也能做聚合统计。
比如:
统计每天订单数量
统计每个品牌的商品数量
统计不同状态订单占比
统计最近一小时错误日志数量
统计热门搜索关键词
这类功能可以用 ES 的 aggregation 实现。
不过要注意,ES 适合做搜索和近实时分析,不适合作为强事务数据库使用。
八、Spring Boot 集成 Elasticsearch 示例
1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2. 定义文档对象
@Document(indexName = "product_index")
public class ProductDocument {
@Id
private Long id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Keyword)
private String brand;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Integer)
private Integer sales;
}
这里有几个点要注意:
name 用 text,因为它需要被搜索和分词
brand 用 keyword,因为品牌通常是精确筛选
category 用 keyword,因为分类一般也是精确过滤
price 用 double,因为价格需要范围查询和排序
sales 用 integer,因为销量需要排序
3. 保存商品文档
@Autowired
private ElasticsearchOperations elasticsearchOperations;
public void saveProduct(ProductDocument product) {
elasticsearchOperations.save(product);
}
4. 商品搜索示例
public List<ProductDocument> searchProducts(String keyword) {
NativeQuery query = NativeQuery.builder()
.withQuery(q -> q.match(m -> m.field("name").query(keyword)))
.build();
SearchHits<ProductDocument> hits =
elasticsearchOperations.search(query, ProductDocument.class);
return hits.stream()
.map(SearchHit::getContent)
.toList();
}
这段代码的作用是:根据用户输入的关键词,在商品名称 name 字段中进行全文检索。
如果要继续加品牌、价格、排序等条件,就可以使用 bool 查询进行组合。
九、项目中常见问题
1. MySQL 和 ES 数据一致性
实际项目中,一般不会把 ES 当成主库。核心业务数据仍然存 MySQL,ES 只是搜索库。
常见同步流程是:
业务数据写入 MySQL
↓
发送 MQ 消息
↓
消费者同步数据到 ES
比如商品价格修改后,先更新 MySQL,再发送消息通知搜索服务更新 ES 中的商品文档。
这里需要注意几个问题:
MQ 消息发送失败怎么办
ES 更新失败怎么办
重复消费会不会导致数据异常
删除数据时 ES 是否同步删除
是否需要定时任务做数据校验
所以 ES 数据同步通常要配合 MQ、重试、幂等和定时校验一起做。
2. Mapping 设计不合理
Mapping 一旦设计错,后期修改成本比较高。
常见问题包括:
订单号用了 text,导致精确查询不准确
商品名称用了 keyword,导致无法分词搜索
价格字段用了字符串,导致范围查询和排序异常
时间字段格式不统一,导致时间范围查询失败
设计 Mapping 时,要先想清楚每个字段的用途:
是否需要全文搜索
是否需要精确过滤
是否需要排序
是否需要聚合统计
是否需要范围查询
字段不是随便建的,而是要根据查询场景来设计。
3. 深分页问题
普通分页一般是:
{
"from": 0,
"size": 10
}
前几页没有问题,但如果用户查到第 10000 页,就会变成:
{
"from": 100000,
"size": 10
}
这种深分页性能很差,因为 ES 需要先取出大量数据,再丢弃前面的结果。
解决方式包括:
限制最大翻页页数
使用 search_after
使用 scroll 做批量数据导出
业务上避免无限翻页
搜索类产品一般也不会允许用户无限往后翻。
4. 消息写入和查询性能
ES 写入压力过大时,可能会影响查询性能。
常见优化方式:
批量写入
合理设置刷新间隔
控制字段数量
避免频繁更新大文档
合理设置分片数量
冷热数据分离
查询慢时,则需要重点检查:
是否使用了深分页
是否聚合字段基数太高
是否查询条件过于复杂
Mapping 是否设计合理
分片数量是否合适
集群资源是否不足
十、Elasticsearch 和其他中间件的关系
在后端系统里,ES 通常不是单独使用,而是和 MySQL、Redis、MQ 配合。
可以这样理解:
MySQL:负责核心业务数据,保证事务一致性
Redis:负责热点缓存,提高访问速度
MQ:负责异步解耦和数据同步
Elasticsearch:负责搜索和分析
比如商品系统中:
商品详情页:优先查 Redis,缓存没有再查 MySQL
商品搜索页:查 Elasticsearch
商品修改:先更新 MySQL,再通过 MQ 同步 ES
商品统计分析:可以用 ES 聚合查询
这样每个组件负责自己擅长的事情,系统整体会更清晰。
总结
Elasticsearch 的核心价值是快速搜索和数据分析。
它通过倒排索引提高全文检索效率,通过分词器提升搜索体验,通过 Query DSL 支持复杂查询,通过分片和副本支持分布式扩展。
学习 ES 不能只记概念,更要理解它在项目中解决什么问题:
商品搜索为什么不用 MySQL LIKE
日志检索为什么适合 ES
term 和 match 分别适合什么字段
Mapping 为什么要提前设计
MySQL 和 ES 为什么要做数据同步
深分页为什么慢
ES 为什么不能替代 MySQL
一句话总结:
MySQL 负责存准,Elasticsearch 负责搜快。
更多推荐




所有评论(0)