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" } // 员工数量(整数类型)
}
}
}
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" }
// 其他字段...
}
}
}
}
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" } // 行业(精确匹配,用于筛选)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 若需修改字段类型怎么办?
需通过「重建索引」实现:
- 创建新索引(含正确的 Mapping);
- 通过
_reindexAPI 将旧索引数据迁移至新索引; - 用别名(Alias)切换读写指向,实现无缝迁移。
# 3.4 删除索引
删除索引会彻底清除所有数据和 Mapping,操作不可逆,需谨慎!
DELETE /company
# 四、文档操作:数据的增删改查
文档是 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
}
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
}
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
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 // 修改员工数量
}
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
}
}
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": {} // 匹配所有文档
}
}
2
3
4
5
6
响应说明:
hits.total:匹配的文档总数;hits.hits:匹配的文档列表(默认前 10 条);_id:文档唯一标识;_source:文档原始数据。
# 4.4.2 全文搜索:匹配分词后的关键词
针对 text 类型字段(如公司名称),会对搜索词分词后匹配:
// 搜索名称中含「测试」或「技术」的公司(text 类型会分词)
GET /company/_search
{
"query": {
"match": {
"name": "测试技术"
}
}
}
2
3
4
5
6
7
8
9
# 4.4.3 精确匹配:完全匹配字段值
针对 keyword 类型字段(如城市、行业),需完全匹配:
// 精确匹配城市为「广州市」的公司
GET /company/_search
{
"query": {
"term": {
"city": "广州市"
}
}
}
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 // 小于
}
}
}
}
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": "计算机" }}
]
}
}
}
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": {} }
}
2
3
4
5
6
7
注意:from + size 过大可能导致性能问题,深分页建议用 search_after。
# 排序(按字段排序)
// 按成立时间倒序(新公司在前),员工数正序(人数少在前)
GET /company/_search
{
"query": { "match_all": {} },
"sort": [
{ "create_time": { "order": "desc" }}, // 降序
{ "employees": { "order": "asc" }} // 升序
]
}
2
3
4
5
6
7
8
9
# 字段筛选(只返回需要的字段)
// 只返回名称、城市和成立时间
GET /company/_search
{
"_source": ["name", "city", "create_time"], // 需返回的字段
"query": { "term": { "city": "广州市" }}
}
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个城市
}
}
}
}
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" // 中心点经纬度
}
}
}
2
3
4
5
6
7
8
9
10
# 五、最佳实践与注意事项
索引设计:
- 合理选择字段类型(如无需全文搜索的字符串用 keyword);
- 避免过度设计(字段不宜过多), Mapping 需提前规划。
性能优化:
- 批量操作(用
_bulkAPI 批量增删改,减少请求次数); - 避免深分页(
from + size不超过 10000,如需深分页用search_after); - 过滤条件优先用
filter(不影响评分,可缓存)。
- 批量操作(用
数据安全:
- 删除索引/文档前确认操作,建议先备份;
- 生产环境开启索引副本(
number_of_replicas),提高可用性。
版本控制:
- 文档更新时可通过
version字段实现乐观锁,避免并发冲突。
- 文档更新时可通过