ElasticSearch文档

不同版本的 ElasticSearch API 可能有所区别,本文基于 7.17 版本。

文档在 ElasticSearch 中是一个可被索引的基础信息单元,与关系型数据库中的一行记录类似,也就是一条数据。

新增文档

在创建完索引后就可以向索引中添加文档数据。

1
2
3
4
5
6
# POST http://localhost:9200/index_name/_doc
{
"title":"ElasticSearch文档",
"category":"技术分享",
"time": 1658909732000
}

由于没有指定唯一标识符,创建文档时会自动生成一个随机字符串,如果需要自定义则需要在创建时指定 POST http://localhost:9200/index_name/_doc/customseq1 。文档添加完成后便会返回以下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "index_name", // 索引名称
"_type": "_doc", // 类型-文档
"_id": "g2Dqb48BtywbFMExMRIr", // 随机生成的唯一标识
"_version": 1, // 文档版本号
"result": "created", // 操作结果,created 表示创建成功
"_shards": {
"total": 2, // 分片总数
"successful": 1, // 成功分片
"failed": 0 // 失败分片
},
"_seq_no": 0,
"_primary_term": 1
}

更新文档

ElasticSearch 更新文档的流程如下:

  1. 查找文档读取_source
  2. 合并原 _source 与新文档的数据
  3. 更新 _source,ElasticSearch 使用乐观锁来保证文档数据的一致性(文档的 version 字段会+1),在成功向内存写入数据后,依赖刷新(refresh)的机制最终向 Lucene 写入数据。
  4. 将更新后的文档作为新文档写入索引(重新索引)
  5. 将旧文档标记为删除。

在使用 ElasticSearch 时,应该避免并发频繁更新文档,因为乐观锁在并发更新的场景会出现锁冲突回滚的问题。向 Lucene 刷新写入数据的过程也会增加额外的开销。另外,执行更新操作后旧的文档数据依旧会被储存,占用了更多的磁盘空间。

在 ElasticSearch 中更新文档数据可以用全量修改和局部修改两种方式,全量修改会将原有数据内容全部覆盖。

  1. 全量修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # POST http://localhost:9200/index_name/g2Dqb48BtywbFMExMRIr
    {
    "title":"ElasticSearch映射",
    "category":"学习笔记",
    "time": 1658909732000
    }
    # 返回信息
    {
    "_index": "index_name",
    "_type": "_doc",
    "_id": "g2Dqb48BtywbFMExMRIr",
    "_version": 2,
    "result": "updated", //updated 表示数据被更新
    "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
    },
    "_seq_no": 2,
    "_primary_term": 1
    }
  2. 局部修改

    1
    2
    3
    4
    5
    6
    7
    8
    // 不修改time字段
    # POST http://localhost:9200/index_name/_update/g2Dqb48BtywbFMExMRIr
    {
    "doc": {
    "title":"ElasticSearch分页查询",
    "category":"技术分享"
    }
    }

删除文档

在 ElasticSearch 中删除文档并不会立即从磁盘上删除相关记录,与删除索引不同,删除文档只是将文档暂时标记为删除(逻辑删除,版本号 _version + 1, “result” 标记为:”deleted”),只有在继续索引更多数据时,Elasticsearch 才有可能会在后台清理已删除的文档。

删除文档的时候,是将新文档写入,同时将旧文档标记为已删除。磁盘空间是否释放取决于新旧文档是否在同一个段文件(segment file)中,因此当 Elasticsearch 在后台的合并段文件的过程中有可能触发旧文档的物理删除。由于分片可能有较多的段文件,有很大的概率新旧文档存储于不同的段文件中从而没有物理删除释放空间,如果想要手动释放空间,可以强制合并段文件,并将 max_num_segments 的值设置为1.

1
# POST http://localhost:9200/_forcemerge

删除文档的方式:

  1. 使用唯一标识删除
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # DELETE http://localhost:9200/index_name/_doc/g2Dqb48BtywbFMExMRIr
    # 返回如下:
    {
    "_index": "index_name",
    "_type": "_doc",
    "_id": "g2Dqb48BtywbFMExMRIr",
    "_version": 2,
    "result": "deleted", // deleted表示删除成功
    "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
    },
    "_seq_no": 2,
    "_primary_term": 1
    }
  2. 使用指定条件删除
1
2
3
4
5
6
7
8
# POST http://localhost:9200/index_name/_delete_by_query
{
"query": {
"match": {
"title": "ElasticSearch文档"
}
}
}

使用指定条件执行批量删除时,可能会发生版本冲突。可以在 URL 中拼接 ?conflicts=proceed 参数来强制继续执行删除,conflicts 参数的默认值是终止操作,将参数值设置为 proceed 代表忽略当前文档的版本冲突并继续更新其他文档。

查询文档

向 ElasticSearch 发起 GET/POST 请求即可查询文档。

1
2
3
4
# 查询索引下的全部文档
# GET http://localhost:9200/index_name/_search
# 带参数条件查询
# GET http://localhost:9200/index_name/_search?q=category=:技术分享

为了避免中文或特殊字符出现乱码的清空,可以使用带JSON请求体来查询文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# POST http://localhost:9200/index_name/_search
# 查询全部文档
{
"query":{
"match_all":{}
}
}
# 根据条件查询
{
"query":{
"match":{
"category":"技术分享"
}
},
"_source":["title"], //只返回指定字段的信息
"sort":{ // 可放入多个字段一起排序,排序字段不能为text类型
"time":{ // 根据time字段降序排序
"order":"desc"
}
}
}

多条件查询

可以使用 bool query 构建多条件查询(与-must-同时全部满足、或-should-满足一条、或取反-must_not-满足一条则排除、过滤filter),其中 must/should 与关系型数据库中的 and/or 条件类似。

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
27
28
29
30
31
32
33
# POST http://localhost:9200/index_name/_search
{
"query":{
"bool":{
"must":[
{
"match":{
"category":"技术分享"
}
} //可传入多个条件
],
"should":[ // 查询标题为"ElasticSearch索引"或"ElasticSearch映射"的文章
{
"match":{
"title":"ElasticSearch索引"
}
},
{
"match":{
"title":"ElasticSearch映射" //不能直接在同一个对象内放置两个match查询,因为JSON对象中每个键(如"match")必须是唯一的
}
}
],
"filter": { //filter子句不参与评分计算,只是简单地过滤出符合条件的文档
"range": { //范围查询
"time": {
"gt": 1658909722000
}
}
}
},
}
}

高级检索

  1. term query - 索引词检索
    把检索串当作一个整体来执行检索,不会对检索串分词。

    1
    2
    3
    4
    5
    6
    7
    {
    "query": {
    "term": {
    "title.keyword": "ElasticSearch"
    }
    }
    }
  2. terms query - in检索
    terms, 相当于多个term检索, 类似于关系型数据库中的 in 关键字的用法, 在某些给定的数据中检索。

    1
    2
    3
    4
    5
    6
    7
    {
    "query": {
    "terms": {
    "id.keyword": ["1", "2"]
    }
    }
    }
  3. prefix query - 前缀检索
    前缀检索会扫描所有的倒排索引,性能较差。
    例:文章标题title中有多个以”ElasticSearch”开头的文档, 检索前缀”ElasticSearch”时就能检索到所有以”ElasticSearch”开头的文档。

    1
    2
    3
    4
    5
    {
    "query": {
    "prefix": { "title.keyword": "ElasticSearch" }
    }
    }
  4. wildcard query - 通配符检索
    通配符检索会扫描所有的倒排索引,性能较差,查询匹配时需要拼接*来模糊检索。使用wildcard时需要注意限制传入参数的数量、参数值的长度,过多的查询参数会导致查询效率低,太长的查询参数值会导致字符串处理时(构建自动机)占用较多资源或抛出异常,甚至集群无响应。
    例:文章标题title中有多个包含”Elastic”的文档,检索*Elastic*时就可以检索到所有包含”Elastic”的文档。

    1
    2
    3
    4
    5
    {
    "query": {
    "wildcard": { "title.keyword": "*Elastic*" }
    }
    }
  5. regexp query - 正则检索
    正则检索会扫描所有的倒排索引,性能较差,查询匹配时可以使用正则+*通配符配合检索。
    例:文章标题title中有多个包含”Elastic”的文档,检索”Elastic”关键字后仍然包含大写字母的文档。

    1
    2
    3
    4
    5
    {
    "query": {
    "regexp": { "title.keyword": "Elastic[A-Z]*" }
    }
    }
  6. fuzzy query - 纠错检索
    纠错检索可以纠正查询条件中的文本错误。查询条件的fuzziness参数的默认值是2,表示最多可以纠错两次,可以通过修改这个值控制纠错次数,但不宜设置的过大,过大的次数将削弱检索条件的作用。即纠错次数太多,导致限定检索结果的检索条件被改变, 失去了限定作用。
    例:文章标题title中有多个包含”ElasticSearch”的文档,检索时丢失了字母h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    "query": {
    "match": {
    "title": {
    "query": "ElasticSearc",
    "fuzziness": 1,
    "operator": "and"
    }
    }
    }
    }
  7. boost评分权重 - 控制文档的优先级别
    通过boost参数,令满足某个条件的文档的得分更高,从而使得其排名更靠前。
    例:有多篇名为”ElasticSearch文档”的文章,需要将”技术分享”类型的文章排在更前面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "query": {
    "bool": {
    "must": [
    { "match": { "title": "ElasticSearch文档" }}
    ],
    "should": [
    {
    "match": {
    "category": {
    "query": "技术分享",
    "boost": 2 // 提升评分权重
    }
    }
    }
    ]
    }
    }
    }
  8. exist query - 存在检索
    ElasticSearch2.x功能,已废弃,不多做说明。

聚合查询

聚合查询可以对索引中的文档进行统计分析,需要注意的是text类型的字段不能进行聚合查询。聚合分类如下:

  • Bucket Aggregation(桶聚合)
    将满足特定条件的文档的集合放置到一个桶里,每一个桶关联一个key,类似于关系型数据库 MySQL 中的 group by 语句。过多的桶聚合会消耗太多的服务器资源,因此 ElasticSearch 支持对桶数量进行限制(默认为65536),调整 search.max_buckets 即可设置限制单个请求中允许的聚合数量。
1
2
3
4
5
6
7
8
9
10
{
// 其他查询条件...
"aggs":{ //聚合操作
"time_group":{ //名称,随意起名
"terms":{
"field": "time" //需要分组的字段
}
}
}
}

查询的返回如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"aggregations": {
"time_group": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1658909732000,
"doc_count": 3
},
{
"key": 1658909752000,
"doc_count": 1
}
]
}
}
}
  • Metric Aggregation(指标聚合)
    数学运算,可以对文档字段进行统计分析。

单值分析:最大值max、最小值min、平均值avg、总和sum、去重cardinality

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
{
// 其他查询条件...
"aggs": {
"earliest": { // 最早时间
"max": {
"field": "time"
}
},
"latest": { // 最晚时间
"min": {
"field": "time"
}
},
"avg": { // 平均时间
"avg": {
"field": "time"
}
},
"no_duplicates": { // 去重
"cardinality": {
"field": "time"
}
}
}
}

多值分析:统计stats、统计条件extended stats、百分比percentile、percentile rank、排行比例top hits

  • Pipeline Aggregation(管道聚合)
    对其他的聚合结果进行二次聚合