8617 字
约 28 分钟
1
Elasticsearch 常用功能全覆盖:从基础概念到项目实战

Elasticsearch 常用功能全覆盖:从基础概念到项目实战

Elasticsearch,简称 ES,是一个基于 Lucene 构建的分布式搜索与分析引擎。

很多人第一次接触 Elasticsearch 时,最容易产生一个误区:

Elasticsearch 是不是一个比 MySQL 更快的数据库?

答案是否定的。

Elasticsearch 的核心定位不是替代 MySQL,而是解决传统关系型数据库不擅长解决的问题,例如:

  • 全文检索
  • 模糊搜索
  • 分词搜索
  • 多条件复杂筛选
  • 搜索相关性排序
  • 搜索结果高亮
  • 海量日志检索
  • 聚合统计分析

在真实项目中,最常见的架构往往是:

MySQL
  │
  │ 数据同步
  ▼
Elasticsearch
  │
  │ 搜索
  ▼
用户

也就是说:

MySQL = 核心业务数据

Elasticsearch = 搜索索引

例如商品系统中:

商品创建
商品修改
商品库存
订单交易

通常还是以 MySQL 为准。

而:

商品搜索
商品筛选
关键词检索
销量排序
价格区间
品牌聚合

则非常适合 Elasticsearch。


一、为什么需要 Elasticsearch?

假设我们有一张商品表:

CREATE TABLE product (
    id BIGINT PRIMARY KEY,
    name VARCHAR(255),
    description TEXT,
    brand VARCHAR(100),
    category VARCHAR(100),
    price DECIMAL(10,2),
    sales INT
);

用户搜索:

苹果手机

最简单的 MySQL 写法可能是:

SELECT *
FROM product
WHERE name LIKE '%苹果手机%'
   OR description LIKE '%苹果手机%';

数据量小时,这种方式还能使用。

但是当数据越来越多,并且搜索需求逐渐变成:

关键词:苹果手机
品牌:Apple
分类:手机
价格:5000~10000
销量:从高到低
支持中文分词
支持高亮
支持相关度排序

MySQL 就会越来越吃力。

而 Elasticsearch 天生就是为这种场景设计的。


二、Elasticsearch 为什么搜索快?

Elasticsearch 最重要的底层思想之一叫:

倒排索引

传统数据库可以简单理解成:

文档1 -> 苹果手机
文档2 -> 华为手机
文档3 -> 小米电视

Elasticsearch 会建立类似这样的结构:

苹果 -> 文档1

手机 -> 文档1、文档2

华为 -> 文档2

小米 -> 文档3

电视 -> 文档3

如果用户搜索:

手机

Elasticsearch 不需要遍历全部数据。

它可以直接找到:

手机 -> 文档1、文档2

这就是倒排索引。

因此,ES 特别擅长:

从大量文本中寻找包含某些关键词的数据

三、Elasticsearch 中几个最重要的概念

学习 ES 前,需要先理解几个基本概念。

1. Index

Index 可以理解成 Elasticsearch 中的一类数据集合。

例如:

products
users
articles
orders
logs

可以粗略类比 MySQL:

MySQL Table
    ≈
Elasticsearch Index

但是两者底层设计完全不同,因此只能作为理解上的类比。


2. Document

Document 就是 Elasticsearch 中的一条数据。

例如:

{
  "id": 1001,
  "name": "Apple iPhone 16 Pro",
  "brand": "Apple",
  "price": 8999
}

可以粗略理解为数据库中的一行。


3. Field

Document 中的字段:

{
  "id": 1001,
  "name": "Apple iPhone 16 Pro",
  "price": 8999
}

这里:

id
name
price

就是 Field。


4. Mapping

Mapping 用来定义字段的数据类型以及字段如何建立索引。

可以类比数据库的表结构。

例如:

PUT /products
{
  "mappings": {
    "properties": {
      "id": {
        "type": "long"
      },
      "name": {
        "type": "text"
      },
      "brand": {
        "type": "keyword"
      },
      "price": {
        "type": "double"
      }
    }
  }
}

四、text 和 keyword 是 ES 最重要的字段区别之一

初学 Elasticsearch,必须真正理解:

text

和:

keyword

的区别。


text

text 表示:

这个字段需要进行全文搜索。

例如:

"name": {
  "type": "text"
}

假设:

Apple iPhone 16 Pro

ES 会对它进行分析和分词。

之后用户搜索:

iphone

也有机会搜索出来。

所以通常:

商品名称
商品描述
文章标题
文章正文

使用:

text

keyword

keyword 表示:

整个字段被当成一个完整值。

例如:

"brand": {
  "type": "keyword"
}

适合:

品牌
状态
类型
SKU
用户名
标签
订单号

这些需要精确匹配的字段。


text + keyword

真实项目中经常会看到:

"name": {
  "type": "text",
  "fields": {
    "keyword": {
      "type": "keyword"
    }
  }
}

这时候 Elasticsearch 会同时提供:

name

和:

name.keyword

两个访问方式。

其中:

name

适合全文搜索。

而:

name.keyword

适合:

精确匹配
排序
聚合

这是 Elasticsearch 中非常常见的设计。


五、创建索引

创建商品索引:

PUT /products
{
  "mappings": {
    "properties": {
      "id": {
        "type": "long"
      },
      "name": {
        "type": "text"
      },
      "description": {
        "type": "text"
      },
      "brandId": {
        "type": "long"
      },
      "brandName": {
        "type": "keyword"
      },
      "categoryId": {
        "type": "long"
      },
      "categoryName": {
        "type": "keyword"
      },
      "price": {
        "type": "double"
      },
      "sales": {
        "type": "integer"
      },
      "status": {
        "type": "integer"
      },
      "createTime": {
        "type": "date"
      }
    }
  }
}

六、查看索引

查看索引:

GET /products

查看 Mapping:

GET /products/_mapping

七、删除索引

DELETE /products

需要特别注意:

删除索引相当于直接删除:

索引结构
+
索引中的全部数据

生产环境操作时必须谨慎。


八、新增 Document

新增商品:

POST /products/_doc/1001
{
  "id": 1001,
  "name": "Apple iPhone 16 Pro",
  "description": "Apple flagship smartphone",
  "brandId": 1,
  "brandName": "Apple",
  "categoryId": 10,
  "categoryName": "Phone",
  "price": 8999,
  "sales": 12000,
  "status": 1
}

这里:

1001

就是 Document ID。


九、根据 ID 查询

GET /products/_doc/1001

这属于精确读取。

但是这里要注意:

如果业务只是:

根据主键 ID 查询一条数据

通常 MySQL 本身就非常擅长。

没必要为了这种查询专门引入 ES。


十、删除 Document

DELETE /products/_doc/1001

十一、更新 Document

可以进行局部更新:

POST /products/_update/1001
{
  "doc": {
    "price": 7999
  }
}

表示:

只修改 price

十二、match:全文搜索最常用查询

假设商品:

Apple iPhone 16 Pro

查询:

GET /products/_search
{
  "query": {
    "match": {
      "name": "iPhone"
    }
  }
}

match 的特点是:

查询内容会经过分析器

因此适合:

全文搜索字段

通常对应:

text

类型。

记忆:

全文搜索
    ↓
match

十三、term:精确查询

假设:

brandName = Apple

查询:

GET /products/_search
{
  "query": {
    "term": {
      "brandName": "Apple"
    }
  }
}

term 通常用于:

精确值匹配

适合:

ID
状态
品牌
类型
分类
SKU

通常搭配:

keyword
long
integer
boolean

等字段。

记忆:

全文搜索 → match

精确匹配 → term

十四、terms:匹配多个精确值

如果需要:

brandId = 1
或者
brandId = 2
或者
brandId = 3

可以:

GET /products/_search
{
  "query": {
    "terms": {
      "brandId": [
        1,
        2,
        3
      ]
    }
  }
}

可以理解为 SQL 中:

WHERE brand_id IN (1,2,3)

十五、range:范围查询

查询价格:

5000 ~ 10000

可以:

GET /products/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 5000,
        "lte": 10000
      }
    }
  }
}

其中:

gt  = >
gte = >=

lt  = <
lte = <=

范围查询特别常见于:

价格
时间
销量
年龄
评分

十六、bool:真实项目最重要的组合查询

真实业务搜索基本不可能只有一个条件。

例如用户搜索:

关键词:iphone
品牌:Apple
价格:5000~10000
状态:上架

这时候通常使用:

bool

例如:

GET /products/_search
{
  "query": {
    "bool": {

      "must": [
        {
          "match": {
            "name": "iphone"
          }
        }
      ],

      "filter": [
        {
          "term": {
            "brandName": "Apple"
          }
        },

        {
          "range": {
            "price": {
              "gte": 5000,
              "lte": 10000
            }
          }
        },

        {
          "term": {
            "status": 1
          }
        }
      ]

    }
  }
}

十七、must、filter、should、must_not

Bool 查询里面有四个非常重要的结构。

must

表示:

必须满足

并且通常参与相关度评分。

例如:

"must": [
  {
    "match": {
      "name": "iphone"
    }
  }
]

filter

同样表示:

必须满足

但是通常不参与相关度评分。

例如:

"filter": [
  {
    "term": {
      "status": 1
    }
  }
]

在项目中:

status
brandId
categoryId
price

这种过滤条件通常放:

filter

而不是:

must

原因是这些条件通常不需要计算:

相关度

should

表示:

最好满足

可以用于提高某些结果的相关度。

例如:

"should": [
  {
    "term": {
      "brandName": "Apple"
    }
  }
]

可以理解为:

满足更好

must_not

表示:

不能满足

例如:

"must_not": [
  {
    "term": {
      "status": 0
    }
  }
]

表示排除:

下架商品

十八、multi_match:多个字段同时搜索

实际项目中,用户搜索关键词时通常不只搜索商品名称。

还可能搜索:

name
description
brand
category

这时候可以:

GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "iphone",
      "fields": [
        "name",
        "description"
      ]
    }
  }
}

这样一个关键词就可以同时搜索多个字段。


十九、搜索字段加权

真实搜索系统通常认为:

商品名称命中

比:

商品描述命中

更加重要。

所以可以提高字段权重:

{
  "multi_match": {
    "query": "iphone",
    "fields": [
      "name^3",
      "description"
    ]
  }
}

其中:

name^3

表示提高 name 字段的重要程度。

这样:

商品名包含 iphone

的结果更容易排到前面。


二十、排序

按照价格升序:

GET /products/_search
{
  "sort": [
    {
      "price": "asc"
    }
  ]
}

按照销量降序:

{
  "sort": [
    {
      "sales": "desc"
    }
  ]
}

多字段排序:

{
  "sort": [
    {
      "sales": "desc"
    },
    {
      "price": "asc"
    }
  ]
}

二十一、相关度排序

如果不指定业务排序,全文搜索通常会根据:

_score

进行相关性排序。

例如:

用户搜索:iphone

ES 会计算哪些商品和:

iphone

更加相关。

匹配程度更高的结果通常会排在前面。

这就是搜索引擎与普通数据库查询非常不同的地方。

数据库常常回答:

符合还是不符合?

搜索引擎还要回答:

哪个结果更符合?

二十二、分页

最基础分页使用:

from
size

例如:

GET /products/_search
{
  "from": 0,
  "size": 20,
  "query": {
    "match": {
      "name": "iphone"
    }
  }
}

其中:

from = 从第几条开始

size = 返回多少条

第 1 页:

from = 0
size = 20

第 2 页:

from = 20
size = 20

Java 中通常:

from = (page - 1) * size;

二十三、为什么不能无限使用 from + size?

假设用户访问:

第 10000 页

每页:

20 条

那么:

from = 199980
size = 20

在分布式 Elasticsearch 中,各个分片需要产生大量候选数据,再由协调节点进行排序和合并。

所以:

from + size

不适合特别深的分页。

这种问题叫:

深度分页

二十四、search_after:深分页常用方案

对于深分页,可以考虑:

search_after

例如先排序:

{
  "size": 20,
  "sort": [
    {
      "sales": "desc"
    },
    {
      "id": "asc"
    }
  ]
}

上一页最后一条:

sales = 10000
id = 9527

下一页可以:

{
  "size": 20,

  "search_after": [
    10000,
    9527
  ],

  "sort": [
    {
      "sales": "desc"
    },
    {
      "id": "asc"
    }
  ]
}

所以:

普通分页
    ↓
from + size

深分页 / 滚动式分页
    ↓
search_after

二十五、高亮

搜索引擎常见需求:

用户搜索:

苹果

页面显示:

最新 <em>苹果</em> 手机

可以使用:

GET /products/_search
{
  "query": {
    "match": {
      "name": "苹果"
    }
  },

  "highlight": {
    "fields": {
      "name": {}
    }
  }
}

ES 返回结果中会多:

"highlight": {
  "name": [
    "最新 <em>苹果</em> 手机"
  ]
}

前端就可以展示高亮。


二十六、_source 字段过滤

如果商品 Document 很大,但搜索结果只需要:

id
name
price
brandName

可以:

GET /products/_search
{
  "_source": [
    "id",
    "name",
    "price",
    "brandName"
  ],

  "query": {
    "match": {
      "name": "iphone"
    }
  }
}

这样可以减少:

返回数据量
网络传输
反序列化成本

二十七、聚合 Aggregation

Elasticsearch 不仅能搜索,也非常擅长统计。

例如搜索手机时,希望统计品牌:

Apple   2000
Huawei  1500
Xiaomi  1200

可以:

GET /products/_search
{
  "size": 0,

  "aggs": {
    "brands": {
      "terms": {
        "field": "brandName"
      }
    }
  }
}

返回类似:

{
  "buckets": [
    {
      "key": "Apple",
      "doc_count": 2000
    },
    {
      "key": "Huawei",
      "doc_count": 1500
    }
  ]
}

二十八、为什么聚合经常使用 keyword?

假设:

brandName

是:

text

它可能会被分词。

聚合希望统计的是完整品牌:

Apple
Huawei
Xiaomi

因此通常使用:

keyword

如果字段 Mapping:

"name": {
  "type": "text",
  "fields": {
    "keyword": {
      "type": "keyword"
    }
  }
}

聚合时通常使用:

name.keyword

而不是:

name

二十九、平均值聚合

计算商品平均价格:

{
  "size": 0,

  "aggs": {
    "avg_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

三十、最大值、最小值、总和

最大值:

{
  "aggs": {
    "max_price": {
      "max": {
        "field": "price"
      }
    }
  }
}

最小值:

{
  "aggs": {
    "min_price": {
      "min": {
        "field": "price"
      }
    }
  }
}

总和:

{
  "aggs": {
    "total_sales": {
      "sum": {
        "field": "sales"
      }
    }
  }
}

三十一、范围聚合

例如商品价格区间:

0~1000

1000~5000

5000~10000

10000+

可以:

{
  "size": 0,

  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",

        "ranges": [
          {
            "to": 1000
          },
          {
            "from": 1000,
            "to": 5000
          },
          {
            "from": 5000,
            "to": 10000
          },
          {
            "from": 10000
          }
        ]
      }
    }
  }
}

这种功能特别适合:

商城筛选
数据报表
日志统计

三十二、组合:搜索 + 筛选 + 聚合

真正项目里,经常不是:

只搜索

或者:

只聚合

而是:

搜索
+
过滤
+
聚合

例如:

搜索 iphone
只看上架商品
同时统计品牌

可以:

GET /products/_search
{
  "query": {
    "bool": {

      "must": [
        {
          "match": {
            "name": "iphone"
          }
        }
      ],

      "filter": [
        {
          "term": {
            "status": 1
          }
        }
      ]

    }
  },

  "aggs": {
    "brands": {
      "terms": {
        "field": "brandName"
      }
    }
  }
}

这就是 Elasticsearch 在电商搜索中非常强大的地方。


三十三、中文分词

英文天然有空格:

Apple iPhone Pro

但是中文:

苹果手机保护壳

没有天然空格。

如果分词不好,搜索体验会非常差。

理想情况下可能希望拆成:

苹果
手机
保护壳

所以中文搜索项目通常会考虑中文分析器和中文分词方案。

例如项目中常见做法是安装合适的中文分析插件,然后在 Mapping 中指定 analyzer。

概念上可以理解:

"name": {
  "type": "text",
  "analyzer": "某个中文分析器"
}

真正生产环境需要结合:

业务词库
产品名称
品牌词
专业词
同义词
搜索日志

调整搜索效果。


三十四、分词效果查看

可以使用 Analyze API 测试:

POST /_analyze
{
  "analyzer": "standard",
  "text": "Apple iPhone 16 Pro"
}

它可以帮助开发者了解:

一段文本最终会被拆成哪些 token

这是分析:

为什么搜索不到
为什么匹配结果不符合预期

时非常重要的工具。


三十五、同义词

真实搜索系统经常需要解决:

苹果
Apple
iPhone

之间的关系。

例如用户搜索:

苹果手机

可能希望匹配:

Apple iPhone

这类需求通常要结合:

同义词
词库
业务规则
搜索权重

实现。

例如逻辑上:

苹果 => apple
手机 => phone

但是实际生产系统不能随便堆同义词。

因为同义词过多可能导致:

召回结果过多
相关度下降
搜索污染

三十六、Bulk 批量操作

如果要一次导入几十万商品,不应该:

循环 100000 次
每次请求 ES 一次

这样网络开销非常大。

Elasticsearch 提供:

Bulk API

例如:

POST /_bulk
{"index":{"_index":"products","_id":"1001"}}
{"id":1001,"name":"iPhone","price":8999}
{"index":{"_index":"products","_id":"1002"}}
{"id":1002,"name":"Huawei Mate","price":6999}

批量操作非常适合:

历史数据导入
索引重建
全量数据同步
批量更新

三十七、真实项目中 MySQL 数据怎么同步到 ES?

这是 ES 项目开发真正重要的地方。

假设管理员修改商品:

iPhone

8999
    ↓
7999

MySQL 更新:

UPDATE product
SET price = 7999
WHERE id = 1001;

但是 ES 中还是:

8999

就产生了数据不一致。

因此项目必须设计:

MySQL → ES

的数据同步机制。


三十八、最简单同步方式

最简单做法:

更新 MySQL
    ↓
更新 ES

伪代码:

updateMysql(product);

updateElasticsearch(product);

优点:

实现简单

缺点:

MySQL 成功
ES 失败

就会产生数据不一致。

而且 ES 响应速度还会影响业务接口。

所以生产环境通常需要更完善的方案。


三十九、MQ 异步同步

一种非常常见的架构:

商品服务
    │
    ▼
MySQL
    │
    ▼
发送 MQ
    │
    ▼
Kafka / RabbitMQ / RocketMQ
    │
    ▼
搜索服务
    │
    ▼
Elasticsearch

例如商品修改后发送:

{
  "type": "PRODUCT_UPDATE",
  "productId": 1001
}

消费者收到:

productId = 1001

然后:

查询最新商品数据
    ↓
构造 ProductDocument
    ↓
更新 Elasticsearch

这样可以降低主业务与 ES 的直接耦合。


四十、为什么 ES 往往是最终一致性?

假设:

12:00:00.000
MySQL 修改成功

12:00:00.100
MQ 消息发送成功

12:00:00.300
消费者处理

12:00:00.500
ES 更新完成

这中间 ES 有短暂时间还是旧数据。

因此:

MySQL
    ↓
MQ
    ↓
ES

通常属于:

最终一致性

而不是:

强一致性

对于:

商品搜索
文章搜索
日志检索

通常可以接受。

但是:

支付
扣库存
余额
订单最终状态

不能只依赖 ES。


四十一、为什么 ES 不应该作为核心交易数据源?

例如搜索结果显示:

库存:10

但这可能是几百毫秒前的数据。

用户真正下单时,应该:

重新查询核心库存

所以:

搜索阶段
    ↓
Elasticsearch

真正交易阶段
    ↓
MySQL / Redis / 核心交易系统

这就是正确的职责边界。


四十二、全量同步

即使使用 MQ,也可能发生:

消息丢失
消费者异常
程序 Bug
ES 故障
历史数据遗漏

所以生产项目经常设计:

增量同步
+
全量同步

增量同步:

MySQL
    ↓
MQ
    ↓
ES

全量同步:

MySQL
    ↓
分页读取
    ↓
Bulk
    ↓
ES

因此,如果 ES 索引出了严重问题,可以:

删除旧索引
    ↓
重新建立 Mapping
    ↓
从 MySQL 全量重建

四十三、为什么 ES Document 经常和数据库实体不一样?

数据库可能有:

product
brand
category
product_attribute

四张表。

MySQL 通过:

JOIN

把它们关联起来。

但是 Elasticsearch 更常采用:

反范式设计

例如 ProductDocument:

{
  "id": 1001,

  "name": "iPhone 16 Pro",

  "brandId": 1,
  "brandName": "Apple",

  "categoryId": 10,
  "categoryName": "Phone",

  "price": 8999
}

虽然:

brandName
categoryName

在数据库里可能存在其他表中,但是 ES 为了搜索方便,直接冗余进 Document。

所以:

数据库设计

关注:

数据规范
事务
数据一致性

而:

Elasticsearch Document

更加关注:

搜索效率
查询方式
搜索需求

四十四、别把 ES 当 MySQL 用

这是非常常见的错误。

很多开发者把 MySQL 表:

product
brand
category

原样建立成:

product_index
brand_index
category_index

然后希望像 SQL 一样各种 JOIN。

这种思维通常不适合 ES。

Elasticsearch 更推荐:

根据搜索场景设计 Document

而不是:

照搬数据库表结构

四十五、搜索系统的核心不是“能搜到”,而是“搜得准”

例如用户搜:

苹果

下面几个结果:

Apple iPhone

苹果手机壳

苹果水果

苹果味饮料

理论上都可能匹配。

但是一个电商手机商城里:

Apple iPhone

显然应该比:

苹果味饮料

更相关。

所以真正的搜索系统需要考虑:

召回率
准确率
相关度
字段权重
同义词
业务权重
用户行为

ES 提供的是搜索能力基础。

真正的:

搜索质量

仍然需要业务设计。


四十六、搜索结果业务加权

有时项目希望:

相关度高
+
销量高
+
评分高

的商品排在前面。

这时候不能只依赖:

_score

可能需要:

相关度
+
业务分数

进行综合排序。

这类问题可以进一步研究:

function_score
script_score

等功能。


四十七、日志搜索也是 ES 的经典场景

除了商品搜索,Elasticsearch 另一个非常经典的用途是:

日志检索

假设系统有:

user-service
order-service
product-service
payment-service

每天产生几千万条日志。

如果开发人员还需要:

SSH 到机器
grep 日志

会非常痛苦。

因此常见架构:

应用日志
    ↓
Agent / Filebeat
    ↓
数据处理
    ↓
Elasticsearch
    ↓
Kibana

开发人员可以搜索:

traceId = xxx

或者:

service = payment-service

level = ERROR

快速定位问题。


四十八、Elasticsearch 适合什么场景?

Elasticsearch 非常适合:

商品搜索

文章搜索

论坛搜索

知识库搜索

日志搜索

用户搜索

订单后台搜索

复杂条件筛选

全文检索

数据聚合分析

尤其是当业务要求:

分词
模糊匹配
相关度
多字段搜索
高亮
聚合

时,ES 的价值会非常明显。


四十九、什么时候不应该用 ES?

不要看到:

数据量大

就直接上 ES。

例如:

根据用户 ID 查询用户

MySQL:

SELECT *
FROM user
WHERE id = ?;

已经非常快。

又例如:

根据订单号查询订单

数据库建立索引以后同样很适合。

因此:

数据量大

并不是使用 Elasticsearch 的充分条件。

更重要的是:

搜索需求复杂

五十、MySQL、Redis、ES 到底怎么选?

可以先记住:

MySQL
    ↓
核心业务数据
事务
精确查询


Redis
    ↓
缓存
热点数据
高频读写
分布式能力


Elasticsearch
    ↓
搜索
全文检索
复杂筛选
聚合分析
日志检索

一句话:

MySQL 负责存准

Redis 负责读快

ES 负责搜好

当然真实系统会更加复杂,但这个理解非常适合建立基础架构认知。


五十一、项目中的典型搜索接口

假设前端请求:

GET /api/products/search

参数:

keyword=iphone
brandId=1
categoryId=10
minPrice=5000
maxPrice=10000
sort=sales
page=1
size=20

搜索条件可以设计为:

bool

├── must
│
└── match keyword


filter

├── brandId
├── categoryId
├── price
└── status


sort

└── sales desc


pagination

├── from
└── size


highlight

└── name


aggregation

├── brand
├── category
└── price range

这基本已经覆盖了大部分商城搜索业务。


五十二、一个较完整的查询示例

例如:

GET /products/_search
{
  "_source": [
    "id",
    "name",
    "brandName",
    "price",
    "sales"
  ],

  "from": 0,
  "size": 20,

  "query": {
    "bool": {

      "must": [
        {
          "multi_match": {
            "query": "iphone",
            "fields": [
              "name^3",
              "description"
            ]
          }
        }
      ],

      "filter": [

        {
          "term": {
            "brandId": 1
          }
        },

        {
          "term": {
            "categoryId": 10
          }
        },

        {
          "term": {
            "status": 1
          }
        },

        {
          "range": {
            "price": {
              "gte": 5000,
              "lte": 10000
            }
          }
        }

      ]

    }
  },

  "sort": [
    {
      "sales": "desc"
    }
  ],

  "highlight": {
    "fields": {
      "name": {}
    }
  },

  "aggs": {

    "brands": {
      "terms": {
        "field": "brandName"
      }
    },

    "categories": {
      "terms": {
        "field": "categoryName"
      }
    }

  }
}

这一条 Query DSL 已经同时包含:

字段过滤

分页

全文搜索

多字段搜索

字段权重

精确筛选

范围筛选

排序

高亮

聚合

基本可以称为一个典型的项目级 Elasticsearch 查询。


五十三、Mapping 设计比你想象中重要

很多 Elasticsearch 性能问题和搜索问题,实际上不是 Query DSL 写错了。

而是:

Mapping 一开始就设计错了。

例如:

应该精确匹配的字段设计成:

text

应该全文搜索的字段设计成:

keyword

或者:

所有字段全部建立索引

最终都会带来问题。

所以设计 Index 前一定要问:

这个字段需要全文搜索吗?

需要过滤吗?

需要排序吗?

需要聚合吗?

需要存储吗?

需要分词吗?

然后再决定 Mapping。


五十四、不要什么字段都存进 ES

例如商品数据库有:

几十个内部管理字段
审核字段
财务字段
运营字段
备注字段

但是搜索只需要:

id
name
description
brand
category
price
sales
status

那就只建立真正需要用于:

搜索
筛选
排序
聚合
展示

的数据。

ES 索引并不是越大越好。


五十五、不要滥用 wildcard

有些开发者习惯把 SQL:

LIKE '%iphone%'

直接翻译成:

wildcard

然后大量使用。

这是危险的。

因为通配符查询尤其是前缀不固定的模式,可能带来比较高的执行成本。

如果需求本质是:

全文搜索

应该优先:

分析器
match
multi_match

而不是一上来就:

wildcard

五十六、避免超大分页

这种请求:

第 50000 页

对于搜索系统本身就不一定合理。

用户真的会翻到第 50000 页吗?

很多搜索产品会限制:

最大可访问结果范围

或者使用:

search_after

支持向后翻页。


五十七、Bulk 大小不是越大越好

Bulk 可以提高写入效率。

但如果一次:

几百万条 Document

全部塞进一个 Bulk 请求,也可能:

占用大量内存
请求过大
GC 压力
节点压力
超时

所以生产环境通常需要:

分批 Bulk

具体批次大小需要结合:

Document 大小
网络
集群配置
节点资源
写入吞吐

压测确定。


五十八、搜索慢首先检查什么?

当查询变慢时,不要第一时间就:

加机器

应该检查:

Mapping 是否合理

查询条件是否合理

是不是 wildcard 太多

是不是深分页

是不是返回字段太多

是不是聚合桶太多

是不是 shard 设计不合理

是不是节点资源不足

是不是写入压力太大

性能优化永远应该:

先定位瓶颈
再优化

五十九、Shard 是什么?

Elasticsearch 是分布式搜索引擎。

一个 Index 可以被拆成多个:

Shard

例如:

products

可能被分成:

Shard 0
Shard 1
Shard 2

数据分布在不同节点。

查询时,各个 Shard 可以分别执行搜索,再把结果进行合并。

这是 Elasticsearch:

水平扩展

的重要基础。


六十、Replica 是什么?

Replica 就是副本。

例如:

Primary Shard

有对应的:

Replica Shard

副本可以提高:

高可用性
读取能力

如果一个节点故障,副本可以帮助保证数据和查询能力。


六十一、为什么 Shard 不是越多越好?

很多初学者会觉得:

Shard 越多
并行度越高
性能越好

实际上错误。

Shard 本身也有:

内存成本
文件句柄
管理成本
查询协调成本

如果只有少量数据,却创建大量 Shard:

100 MB 数据
100 个 Shard

往往反而更加低效。

所以 Shard 数量需要结合:

索引体量
节点数量
查询压力
写入压力
未来增长

设计。


六十二、ES 为什么是 Near Real Time?

Elasticsearch 经常被称为:

近实时搜索

因为数据写进去后,不一定在同一个瞬间立刻对搜索可见。

这是因为 Elasticsearch 会经过类似:

写入
    ↓
buffer
    ↓
refresh
    ↓
searchable

的过程。

所以项目中不要错误地假设:

ES 写成功
=
下一微秒所有查询一定立刻看到

对于搜索业务来说,通常这种近实时特性完全可以接受。


六十三、ES 不适合强事务场景

Elasticsearch 不是为:

银行转账
支付事务
库存严格扣减
账户余额

这类强事务业务设计的。

这些场景应该使用:

关系型数据库
事务系统

ES 更适合:

搜索型数据
分析型数据
日志型数据

六十四、Spring Boot 项目中的对象设计

例如数据库实体:

public class Product {

    private Long id;

    private String name;

    private Long brandId;

    private Long categoryId;

    private BigDecimal price;

    private Integer stock;
}

而 ES Document:

public class ProductDocument {

    private Long id;

    private String name;

    private Long brandId;

    private String brandName;

    private Long categoryId;

    private String categoryName;

    private BigDecimal price;

    private Integer sales;
}

可以看到:

Product

和:

ProductDocument

并不一定完全一样。

因为:

Product

服务于:

业务数据库

而:

ProductDocument

服务于:

搜索

六十五、真实项目的数据流

商品修改:

管理员
    ↓
ProductController
    ↓
ProductService
    ↓
MySQL
    ↓
发送 MQ
    ↓
Search Consumer
    ↓
Elasticsearch

用户搜索:

用户
    ↓
SearchController
    ↓
SearchService
    ↓
Elasticsearch
    ↓
搜索结果
    ↓
前端

这就是非常典型的 Elasticsearch 项目架构。


六十六、一个 ES 项目的完整知识体系

真正掌握 Elasticsearch,可以按照下面顺序学习:

第一阶段

Index
Document
Field
Mapping
text
keyword


第二阶段

match
term
terms
range
bool


第三阶段

must
filter
should
must_not


第四阶段

multi_match
排序
分页
高亮


第五阶段

Aggregation
terms
avg
sum
min
max
range


第六阶段

Analyzer
中文分词
同义词
字段权重
相关度


第七阶段

Bulk
全量同步
增量同步
MQ


第八阶段

Shard
Replica
Refresh
深分页


第九阶段

搜索性能优化
Mapping 优化
索引设计
数据一致性

学到这里,已经可以覆盖大部分普通业务项目中 Elasticsearch 的使用场景。


六十七、ES 最常见面试问题

实际面试中,经常会被问:

为什么使用 Elasticsearch?

ES 和 MySQL 有什么区别?

什么是倒排索引?

text 和 keyword 有什么区别?

match 和 term 有什么区别?

must 和 filter 有什么区别?

ES 如何实现分页?

深分页怎么解决?

ES 数据和 MySQL 如何同步?

为什么用 MQ 同步?

ES 数据不一致怎么办?

什么是 Shard?

什么是 Replica?

ES 为什么搜索快?

Elasticsearch 为什么不是强实时?

你们项目 ES 用在哪?

如果真正理解前面的内容,这些问题基本都可以回答。


六十八、项目中什么时候应该使用 ES?

最后可以使用这个判断标准。

如果需求只是:

根据 ID 查数据

使用:

MySQL

如果需求是:

事务

使用:

MySQL

如果需求是:

缓存热点数据

使用:

Redis

如果需求开始出现:

关键词搜索

全文检索

分词

模糊搜索

多字段搜索

相关度排序

复杂筛选

高亮

聚合分析

海量日志检索

那么:

Elasticsearch

就开始真正发挥价值。


总结

Elasticsearch 最重要的能力可以概括成:

全文检索
+
倒排索引
+
分词
+
相关度
+
复杂过滤
+
排序
+
分页
+
高亮
+
聚合
+
分布式

实际项目中的典型架构是:

                 MySQL
              主数据来源
                  │
                  │
             MQ / 数据同步
                  │
                  ▼
            Elasticsearch
              搜索索引
                  │
                  ▼
               用户搜索

一定要记住:

Elasticsearch 的最大价值不是“代替数据库”,而是把复杂搜索这件事情做好。

所以真正学习 ES,不应该只会背:

match
term
bool
range

而应该建立完整思维:

为什么需要搜索引擎
        ↓
数据怎么设计
        ↓
Mapping 怎么设计
        ↓
数据怎么进入 ES
        ↓
用户怎么搜索
        ↓
查询怎么组合
        ↓
结果怎么排序
        ↓
怎么高亮
        ↓
怎么聚合
        ↓
怎么分页
        ↓
怎么保证 MySQL 和 ES 最终一致
        ↓
怎么处理性能问题

当这整条链路真正串起来之后,Elasticsearch 才算真正从“会写几个 DSL”升级成“会在项目里使用 Elasticsearch”。

Elasticsearch 常用功能全覆盖:从基础概念到项目实战
http://clxhxhhr.top/posts/398/
作者
clxstart
发布于
2026-09-01
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。
文章目录
目录