Tavio's blog Tavio's blog
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Tavio Zhang

努力学习的小码喽
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • ES CRUD
    • 一、Elasticsearch 核心概念与价值
    • 二、从 MySQL 视角理解 ES 核心概念
    • 三、索引操作:数据结构的定义与管理
      • 3.1 创建索引(定义 Mapping)
      • 基础语法
      • 常用字段类型说明
      • 注意事项
      • 3.2 查看索引映射
      • 3.3 修改索引(仅支持添加字段)
      • 语法:添加字段
      • 若需修改字段类型怎么办?
      • 3.4 删除索引
    • 四、文档操作:数据的增删改查
      • 4.1 添加文档
      • 自动生成 _id(适合无需自定义 ID 的场景)
      • 自定义 _id(适合需关联业务 ID 的场景)
      • 4.2 删除文档
      • 4.3 修改文档
      • 全量替换(覆盖文档)
      • 部分更新(仅修改指定字段)
      • 4.4 搜索文档:从基础到复杂查询
      • 4.4.1 基础搜索:查询所有文档
      • 4.4.2 全文搜索:匹配分词后的关键词
      • 4.4.3 精确匹配:完全匹配字段值
      • 4.4.4 范围查询:筛选数值/日期范围
      • 4.4.5 组合条件查询:多条件逻辑组合
      • 4.4.6 搜索结果处理:分页、排序与筛选
      • 分页(控制返回数量)
      • 排序(按字段排序)
      • 字段筛选(只返回需要的字段)
      • 4.4.7 聚合分析:实时统计与分析
      • 4.4.8 地理位置搜索:查找附近的文档
    • 五、最佳实践与注意事项
  • ES分布式架构
  • ES ISR机制
  • ES深度分页查询优化
  • ES数据写入到查询
  • ELK的底层逻辑
  • ES ILM
  • 《Elasticsearch》笔记
Tavio
2025-05-12
目录

ES CRUD

Elasticsearch(简称 ES)是一款开源的分布式搜索引擎,以实时全文检索、高扩展性和灵活的数据分析能力著称。其核心设计围绕「文档(Document)」展开,所有操作均通过 RESTful API 实现,与开发语言无关,仅需通过 HTTP 方法(PUT/POST/GET/DELETE)配合 JSON 格式即可完成交互。

# 一、Elasticsearch 核心概念与价值

相较于传统关系型数据库,ES 更擅长处理非结构化/半结构化数据(如日志、文本、地理位置等),并支持复杂的全文搜索、聚合分析和地理位置查询,广泛应用于日志分析、电商搜索、监控系统等场景。

# 二、从 MySQL 视角理解 ES 核心概念

对于熟悉 MySQL 的开发者,可通过以下对应关系快速掌握 ES 核心概念:

ES 概念 MySQL 概念 核心作用
Index(索引) Database(数据库) 存储相似结构文档的容器,一个索引可理解为一个「数据集合」
Document(文档) Row(行) 一条完整的数据记录,ES 中以 JSON 格式存储,每个文档有唯一 _id 标识
Field(字段) Column(列) 文档的属性(如「姓名」「时间」),对应 JSON 中的键值对
Mapping(映射) Table Schema(表结构) 定义文档中字段的类型、分词规则等元数据,相当于「数据结构说明书」

关键区别:
ES 是「搜索引擎」而非「数据库」,其底层基于倒排索引实现高效全文检索,而 MySQL 基于 B+ 树实现关系型数据存储,二者适用场景互补(如 ES 适合搜索,MySQL 适合事务性存储)。

# 三、索引操作:数据结构的定义与管理

索引是 ES 中数据存储的顶层容器,创建索引时需通过 Mapping 定义字段规则(类似 MySQL 建表时定义字段类型)。

# 3.1 创建索引(定义 Mapping)

创建索引的核心是声明字段类型(Mapping),ES 支持多种字段类型,需根据业务场景选择(如全文搜索用 text,精确匹配用 keyword)。

# 基础语法

PUT /索引名
{
  "mappings": {
    "properties": {
      "字段名1": { "type": "字段类型", "可选参数": "值" },
      "字段名2": { "type": "字段类型" }
    }
  }
}

// 示例:创建「公司信息」索引
PUT /company
{
  "mappings": {
    "properties": {
      "name": { "type": "text" }, // 公司名称(支持全文搜索,会被分词)
      "city": { "type": "keyword" }, // 城市(精确匹配,不分词)
      "create_time": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }, // 成立时间(自定义格式)
      "address": { "type": "geo_point" }, // 地址(经纬度,支持地理位置查询)
      "employees": { "type": "integer" } // 员工数量(整数类型)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 常用字段类型说明

字段类型 适用场景 注意事项
text 全文搜索(如文章内容、商品名称) 会被分词器拆分(如「Java开发」拆分为「Java」「开发」),不支持聚合操作
keyword 精确查询/聚合(如城市、状态、标签) 不分词,需完全匹配(如「广州市」只能匹配「广州市」)
数字类型(long、integer 等) 数值存储(如数量、价格) 支持范围查询(如 >100、<=1000)
date 日期时间(如创建时间、过期时间) 支持自定义格式(默认兼容 "yyyy-MM-dd"、时间戳等),需统一格式避免解析错误
geo_point 地理位置(经纬度点,如店铺位置、用户坐标) 支持「附近搜索」「距离排序」等地理查询
boolean 布尔值(如是否启用、是否删除) 可接受 true/false、"true"/"false" 或 1/0

# 注意事项

  • 索引名称需小写,且不能包含空格、斜杠等特殊字符;
  • 若不声明 Mapping,ES 会根据第一条文档自动推断字段类型(动态映射),但可能存在误差(如数字被识别为 text),建议显式定义 Mapping;
  • ES 无专门数组类型,任何字段均可存储数组(如 ["Java", "Python"]),但需保证数组内元素类型一致。

# 3.2 查看索引映射

查看已创建索引的字段定义,确认 Mapping 是否符合预期:

GET /company/_mapping

// 响应示例(简化):
{
  "company": {
    "mappings": {
      "properties": {
        "name": { "type": "text" },
        "city": { "type": "keyword" }
        // 其他字段...
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3.3 修改索引(仅支持添加字段)

ES 的索引 Mapping 一旦创建,字段类型不可修改(因底层倒排索引结构不可变),但可新增字段:

# 语法:添加字段

PUT /索引名/_mapping
{
  "properties": {
    "新字段名": { "type": "字段类型" }
  }
}

// 示例:为 company 索引添加「行业」字段
PUT /company/_mapping
{
  "properties": {
    "industry": { "type": "keyword" } // 行业(精确匹配,用于筛选)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 若需修改字段类型怎么办?

需通过「重建索引」实现:

  1. 创建新索引(含正确的 Mapping);
  2. 通过 _reindex API 将旧索引数据迁移至新索引;
  3. 用别名(Alias)切换读写指向,实现无缝迁移。

# 3.4 删除索引

删除索引会彻底清除所有数据和 Mapping,操作不可逆,需谨慎!

DELETE /company
1

# 四、文档操作:数据的增删改查

文档是 ES 中最小的数据单元(类似 MySQL 的行),以 JSON 格式存储,每个文档有唯一 _id(可自定义或由 ES 自动生成)。

# 4.1 添加文档

# 自动生成 _id(适合无需自定义 ID 的场景)

使用 POST 方法,ES 会自动生成唯一字符串作为 _id:

POST /索引名/_doc
{
  "字段1": "值1",
  "字段2": "值2"
}

// 示例:
POST /company/_doc
{
  "name": "广州测试技术公司",
  "city": "广州市",
  "create_time": "2022-01-11 20:40:00",
  "address": "22.9982,113.3737", // 经纬度格式:纬度,经度
  "industry": "计算机",
  "employees": 50
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 自定义 _id(适合需关联业务 ID 的场景)

使用 PUT 方法,指定 _id(如业务主键):

PUT /索引名/_doc/自定义ID
{
  "字段1": "值1",
  "字段2": "值2"
}

// 示例(指定 ID 为 1001):
PUT /company/_doc/1001
{
  "name": "深圳Java开发公司",
  "city": "深圳市",
  "create_time": "2021-05-20 10:30:00",
  "address": "22.5431,114.0579",
  "industry": "软件",
  "employees": 120
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4.2 删除文档

通过 _id 删除单条文档:

DELETE /索引名/_doc/文档ID

// 示例(删除 ID 为 1001 的文档):
DELETE /company/_doc/1001
1
2
3
4

注意:删除文档不会立即释放磁盘空间,而是标记为删除,后续通过段合并(segment merge)清理。

# 4.3 修改文档

ES 支持两种修改方式:全量替换和部分更新。

# 全量替换(覆盖文档)

本质是「删除旧文档 + 插入新文档」,需包含所有字段(未指定的字段会丢失):

PUT /索引名/_doc/文档ID
{
  "字段1": "新值1",
  "字段2": "新值2" // 需包含所有要保留的字段
}

// 示例(替换 ID 为 1001 的文档):
PUT /company/_doc/1001
{
  "name": "深圳Java开发公司(更新后)", // 修改名称
  "city": "深圳市", // 保留原字段
  "create_time": "2021-05-20 10:30:00",
  "address": "22.5431,114.0579",
  "industry": "软件",
  "employees": 150 // 修改员工数量
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 部分更新(仅修改指定字段)

更高效(无需传输完整文档),通过 _update API 实现:

POST /索引名/_update/文档ID
{
  "doc": {
    "字段1": "新值1", // 仅指定需修改的字段
    "字段2": "新值2"
  }
}

// 示例(仅修改 ID 为 1001 的员工数量):
POST /company/_update/1001
{
  "doc": {
    "employees": 180
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 4.4 搜索文档:从基础到复杂查询

ES 的搜索能力是其核心优势,支持全文检索、条件组合、范围筛选等多种场景。

# 4.4.1 基础搜索:查询所有文档

GET /company/_search
{
  "query": {
    "match_all": {} // 匹配所有文档
  }
}
1
2
3
4
5
6

响应说明:

  • hits.total:匹配的文档总数;
  • hits.hits:匹配的文档列表(默认前 10 条);
  • _id:文档唯一标识;
  • _source:文档原始数据。

# 4.4.2 全文搜索:匹配分词后的关键词

针对 text 类型字段(如公司名称),会对搜索词分词后匹配:

// 搜索名称中含「测试」或「技术」的公司(text 类型会分词)
GET /company/_search
{
  "query": {
    "match": {
      "name": "测试技术"
    }
  }
}
1
2
3
4
5
6
7
8
9

# 4.4.3 精确匹配:完全匹配字段值

针对 keyword 类型字段(如城市、行业),需完全匹配:

// 精确匹配城市为「广州市」的公司
GET /company/_search
{
  "query": {
    "term": {
      "city": "广州市"
    }
  }
}
1
2
3
4
5
6
7
8
9

# 4.4.4 范围查询:筛选数值/日期范围

// 查询 2020 年之后成立的公司(日期范围)
GET /company/_search
{
  "query": {
    "range": {
      "create_time": {
        "gte": "2020-01-01 00:00:00", // 大于等于
        "lte": "now" // 小于等于当前时间
      }
    }
  }
}

// 查询员工数在 100-500 之间的公司(数值范围)
GET /company/_search
{
  "query": {
    "range": {
      "employees": {
        "gt": 100, // 大于
        "lt": 500  // 小于
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 4.4.5 组合条件查询:多条件逻辑组合

通过 bool 查询组合多个条件(must/should/filter/must_not):

条件类型 作用 是否影响评分
must 必须满足(类似 AND) 是
should 至少满足一个(类似 OR) 是
filter 必须满足(过滤) 否(性能更好)
must_not 必须不满足(类似 NOT) 否

示例:

// 条件:
// 1. 名称含「Java」(must)
// 2. 城市是广州或深圳(should,至少1个)
// 3. 成立时间在2020年后(filter)
// 4. 行业不是「计算机」(must_not)
GET /company/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "Java" }}
      ],
      "should": [
        { "term": { "city": "广州市" }},
        { "term": { "city": "深圳市" }}
      ],
      "minimum_should_match": 1, // 至少满足1个should条件
      "filter": [
        { "range": { "create_time": { "gte": "2020-01-01" }}}
      ],
      "must_not": [
        { "term": { "industry": "计算机" }}
      ]
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 4.4.6 搜索结果处理:分页、排序与筛选

# 分页(控制返回数量)
// 第2页(从第10条开始),每页10条
GET /company/_search
{
  "from": 10, // 起始位置(0为第一条)
  "size": 10, // 每页条数
  "query": { "match_all": {} }
}
1
2
3
4
5
6
7

注意:from + size 过大可能导致性能问题,深分页建议用 search_after。

# 排序(按字段排序)
// 按成立时间倒序(新公司在前),员工数正序(人数少在前)
GET /company/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "create_time": { "order": "desc" }}, // 降序
    { "employees": { "order": "asc" }}     // 升序
  ]
}
1
2
3
4
5
6
7
8
9
# 字段筛选(只返回需要的字段)
// 只返回名称、城市和成立时间
GET /company/_search
{
  "_source": ["name", "city", "create_time"], // 需返回的字段
  "query": { "term": { "city": "广州市" }}
}
1
2
3
4
5
6

# 4.4.7 聚合分析:实时统计与分析

在搜索的同时对数据进行统计(如分组计数、平均值计算)。

示例:按城市统计含「Java」的公司数量

GET /company/_search
{
  "size": 0, // 不返回原始文档,只看聚合结果
  "query": { "match": { "name": "Java" }},
  "aggs": {
    "city_count": { // 自定义聚合名称
      "terms": {
        "field": "city", // 按city(keyword类型)分组
        "size": 10 // 返回前10个城市
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

响应中 aggregations.city_count.buckets 会返回每个城市的公司数量。

# 4.4.8 地理位置搜索:查找附近的文档

基于 geo_point 字段,搜索指定位置周边的文档:

// 搜索广州南站(22.9982, 113.3737)周边5公里内的公司
GET /company/_search
{
  "query": {
    "geo_distance": {
      "distance": "5km", // 距离范围(支持 km/m 等单位)
      "address": "22.9982, 113.3737" // 中心点经纬度
    }
  }
}
1
2
3
4
5
6
7
8
9
10

# 五、最佳实践与注意事项

  1. 索引设计:

    • 合理选择字段类型(如无需全文搜索的字符串用 keyword);
    • 避免过度设计(字段不宜过多), Mapping 需提前规划。
  2. 性能优化:

    • 批量操作(用 _bulk API 批量增删改,减少请求次数);
    • 避免深分页(from + size 不超过 10000,如需深分页用 search_after);
    • 过滤条件优先用 filter(不影响评分,可缓存)。
  3. 数据安全:

    • 删除索引/文档前确认操作,建议先备份;
    • 生产环境开启索引副本(number_of_replicas),提高可用性。
  4. 版本控制:

    • 文档更新时可通过 version 字段实现乐观锁,避免并发冲突。
编辑 (opens new window)
#ES CRUD
上次更新: 2026/01/21, 19:29:14
ES分布式架构

ES分布式架构→

最近更新
01
订单超时取消
01-21
02
双 Token 登录
01-21
03
长短链接跳转
01-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式