Elasticsearch文档操作

1. 前言

Elasticsearch索引是一组相关文档的集合,文档在Elasticsearch中用JSON来表示,每个文档都有一个唯一的"_id"字段来标识。每个文档又是一组字段的集合,字段可以有自己的数据类型,可以是数字、字符串、日期、布尔类型等,Elasticsearch可以索引文档,并对索引的文档做检索和数据分析。

2. 新增文档

我们已经知道,每个文档都有一个唯一的"_id"字段标识。新增文档时,我们既可以指定ID,也可以不指定ID,不指定ID时Elasticsearch会自动生成基于Base64编码长度为20的GUID字符串。

下面是指定ID索引文档的请求:

json 复制代码
PUT items/_doc/1
{
  "title":"苹果",
  "price":5
}

下面是不指定ID索引文档的请求:

json 复制代码
POST items/_doc
{
  "title":"香蕉",
  "price":3
}

可以看到,系统自动生成的ID值为"h1e65Y4BXAgLe9UU1-xS"

json 复制代码
{
  "_index": "items",
  "_id": "h1e65Y4BXAgLe9UU1-xS",
  "_score": 1,
  "_source": {
    "title": "香蕉",
    "price": 3
  }
}

你也许会好奇,items索引压根就不存在,为什么文档可以索引成功呢?这是因为Elasticsearch默认会自动创建索引,可以配置action.auto_create_index 来关闭,如下所示:

json 复制代码
PUT _cluster/settings
{
  "persistent": {
    "action.auto_create_index":false
  }
}

再次发起一个索引不存在的文档索引请求,会得到一个异常

json 复制代码
{
  "error": {
    "root_cause": [
      {
        "type": "index_not_found_exception",
        "reason": "no such index [items-1] and [action.auto_create_index] is [false]",
        "index_uuid": "_na_",
        "index": "items-1"
      }
    ],
    "type": "index_not_found_exception",
    "reason": "no such index [items-1] and [action.auto_create_index] is [false]",
    "index_uuid": "_na_",
    "index": "items-1"
  },
  "status": 404
}

Elasticsearch默认会自动创建不存在的索引,并根据索引的文档数据动态映射字段类型,在这个例子中,price被映射为long类型,title被映射为text和keyword多数据类型。

json 复制代码
GET items/_mapping

{
  "items": {
    "mappings": {
      "properties": {
        "price": {
          "type": "long"
        },
        "title": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        }
      }
    }
  }
}

除了单文档的添加,Elasticsearch还支持批量添加文档,对应的API是 _bulk,对于大量文档的写入,批量操作可以显著提升性能。如下示例,批量写入两个文档:

json 复制代码
POST items/_bulk
{"index":{"_id":"1"}}
{"title":"苹果", "price":5}
{"index":{"_id":"2"}}
{"title":"香蕉", "price":3}

文档与文档之间必须通过换行符来分割,除了索引请求,还可以批量处理删除、更新请求,如下请求,它在items索引下添加了两篇文档、删除了一篇文档、同时更新了orders索引下的一篇文档。

json 复制代码
POST _bulk
{"index":{"_index":"items","_id":"1"}}
{"title":"苹果", "price":5}
{"create":{"_index":"items","_id":"2"}}
{"title":"香蕉", "price":3}
{"update":{"_index":"orders","_id":"1"}}
{"doc":{"order_amount":600}}
{"delete":{"_index":"items","_id":"3"}}

关于create和index的区别是:对于相同的文档,create请求会失败,而index总是会成功,已存在的文档index请求会更新文档。

3. 更新文档

更新文档操作分为:部分更新和全量更新,部分更新即更新文档的部分字段,全量更新则是直接替换整个文档。

部分更新操作,如下示例,把1号文档的title改为"新鲜苹果",price字段仍然会保留

json 复制代码
POST items/_update/1
{
  "doc": {
    "title":"新鲜苹果"
  }
}

GET items/_doc/1

{
  "_index": "items",
  "_id": "1",
  "_version": 2,
  "_seq_no": 2,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "title": "新鲜苹果",
    "price": 5
  }
}

全量更新操作会直接覆盖原有文档,本质上是通过index API来完成的,如下示例,price字段会丢失:

json 复制代码
PUT items/_doc/1
{
  "title":"红富士苹果"
}

GET items/_doc/1

{
  "_index": "items",
  "_id": "1",
  "_version": 3,
  "_seq_no": 3,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "title": "红富士苹果"
  }
}

除此之外,Elasticsearch还支持upsert操作,它类似于关系数据库中的insertOrUpdate,如果文档存在则执行更新操作,不存在则执行写入操作。

如下示例,如果不存在ID=1的文档,则写入upsert部分内容,如果存在ID=1的文档,则更新doc部分的内容。

json 复制代码
POST items/_update/1
{
  "doc": {
    "place_of_production": "山东烟台"
  },
  "upsert": {
    "title": "苹果",
    "price": 5
  }
}

第一次请求时没有place_of_production字段,第二次请求时,因为文档已经存在,则会写入place_of_production。

除了根据指定ID更新单个文档,Elasticsearch同样支持根据搜索条件批量更新文档,对应的API是_update_by_query。批量更新文档首先需要提供"query"部分设定要更新文档的匹配条件,再提供"script"部分即文档更新的脚本。

如下示例,"match_all"匹配所有文档,将所有商品的价格上调1元钱

json 复制代码
POST items/_update_by_query
{
  "query": {
    "match_all": {}
  },
  "script": {
    "lang": "painless",
    "source": "ctx._source.price+=1"
  }
}

文档更新结果:

json 复制代码
[
  {
    "_index": "items",
    "_id": "1",
    "_score": 1,
    "_source": {
      "price": 6,
      "title": "苹果"
    }
  },
  {
    "_index": "items",
    "_id": "2",
    "_score": 1,
    "_source": {
      "price": 4,
      "title": "香蕉"
    }
  }
]

4. 删除文档

删除文档同样分为根据ID单个删除和批量删除文档。需要说明的是,执行删除操作后,Elasticsearch并不会立即将文档从磁盘中物理删除掉,这是因为Elasticsearch采用段的设计来存储文档,每个段都是独立且不可变的索引结构,这么做可以提高查询性能,删除操作仅仅是将文档标记为"已删除",在后续"段合并"阶段Elasticsearch才会真正将这些被标记为已删除的文档真正的删除并释放存储空间。

如下示例,删除items索引下的1号文档:

json 复制代码
DELETE items/_doc/1

返回结果中,result=deleted代表删除成功,同时_version会自增1。

json 复制代码
{
  "_index": "items",
  "_id": "1",
  "_version": 2,
  "result": "deleted",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 1,
  "_primary_term": 1
}

还可以根据条件来批量删除文档,对应的API是_delete_by_query,如下示例,将价格大于等于4元的商品全部删除:

json 复制代码
POST items/_delete_by_query
{
  "query": {
    "range": {
      "price": {
        "gte": 4
      }
    }
  }
}

返回结果中,deleted=2代表删除了两篇符合要求的文档

json 复制代码
{
  "took": 37,
  "timed_out": false,
  "total": 2,
  "deleted": 2,
  "batches": 1,
  "version_conflicts": 0,
  "noops": 0,
  "retries": {
    "bulk": 0,
    "search": 0
  },
  "throttled_millis": 0,
  "requests_per_second": -1,
  "throttled_until_millis": 0,
  "failures": []
}

5. 迁移文档

除了常规的增删改查,Elasticsearch还支持将一个索引的文档迁移到另一个索引中,可以是集群内迁移,也支持跨集群迁移。

对应的API是_reindex,什么时候需要用到reindex呢?索引的一部分配置是静态的,一旦创建好就不允许更改了,比如:主分片的数量,字段的数据类型等,要更改这些静态配置就需要reindex重新构建索引。

举个例子,现在有一个news索引存储新闻数据,包含新闻的标题和评论:

json 复制代码
POST news/_doc
{
  "title": "女子在小区车库遇1米多高阿富汗猎犬",
  "comments": [
    {
      "user":"张三",
      "content":"遛狗不拴绳,等于狗遛人"
    },
    {
      "user":"李四",
      "content":"那最后是什么处理结果呢"
    },
    {
      "user":"王五",
      "content":"大型犬太危险了"
    }
  ]
}

默认情况下,Elasticsearch会把comments字段动态映射为Object类型,comments数组会被构建成一个扁平的键值对数组,从而丢失了单个对象之间的关系,如下所示:

json 复制代码
{
  "comments.user":["张三","李四","王五"],
  "comments.content":["遛狗不拴绳,等于狗遛人","那最后是什么处理结果呢","大型犬太危险了"]
}

现在我们要搜索:张三评论了包含"危险"词语的新闻,搜索条件如下所示:

json 复制代码
GET news/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "comments.user": "张三"
          }
        },
        {
          "match": {
            "comments.content": "危险"
          }
        }
      ]
    }
  }
}

因为Object类型的关系,文档竟然被意外的返回了,这明显与我们的需求不符,这个时候就需要把comments字段更改为nested数据类型,让comments数组的每个对象都单独存储。这种操作不能在news索引上直接操作,所以我们需要创建一个新的索引,然后通过reindex操作来做数据迁移。

新索引news-nested创建命令如下所示,comments类型改为nested,同时新增一个comments_count字段记录评论数:

json 复制代码
PUT news-nested
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text"
      },
      "comments":{
        "type": "nested"
      },
      "comments_count":{
        "type": "integer"
      }
    }
  }
}

接着,调用reindex API完成数据迁移,指定源索引和目标索引,以及迁移过程中要执行的脚本:

json 复制代码
POST _reindex
{
  "source": {
    "index": "news"
  },
  "dest": {
    "index": "news-nested"
  },
  "script": {
    "source": "ctx._source.comments_count = ctx._source.comments.length"
  }
}

查看目标索引的数据,发现文档迁移成功,且comments_count字段有了

json 复制代码
[
  {
    "_index": "news-nested",
    "_id": "lFcm5o4BXAgLe9UUo-wj",
    "_score": 1,
    "_source": {
      "comments": [
        {
          "user": "张三",
          "content": "遛狗不拴绳,等于狗遛人"
        },
        {
          "user": "李四",
          "content": "那最后是什么处理结果呢"
        },
        {
          "user": "王五",
          "content": "大型犬太危险了"
        }
      ],
      "comments_count": 3,
      "title": "女子在小区车库遇1米多高阿富汗猎犬"
    }
  }
]

再次执行nested搜索,结果没有召回任何文档,符合需求。

json 复制代码
GET news-nested/_search
{
  "profile": false,
  "query": {
    "bool": {
      "must": [
        {
          "nested": {
            "path": "comments",
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "comments.user": "张三"
                    }
                  },
                  {
                    "match": {
                      "comments.content": "危险"
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

6. 尾巴

Elasticsearch索引是一系列相关文档的集合,文档又是一组字段的集合,每个字段有自己的数据类型,Elasticsearch可以索引并检索和分析文档数据。文档的增删改操作即可以针对单个文档进行,也可以批量处理,当面临大量操作时,使用批量API可以大幅提升性能。最后,当需要修改索引的静态配置时,可以通过reindex API在索引间迁移数据。

相关推荐
向阳12181 小时前
Kafka快速入门
java·大数据·分布式·kafka·mq
程序员小潘2 小时前
初识Flink
大数据·flink
happy_king_zi2 小时前
Flink On kubernetes
大数据·flink·kubernetes
间彧2 小时前
ELK简介及Head插件
elasticsearch
向阳12183 小时前
Flink入门
大数据·flink
程序员小潘3 小时前
Elasticsearch字段数据类型
大数据·elasticsearch
一勺菠萝丶3 小时前
如何解决Elasticsearch容器因“Connection refused”导致的问题
大数据·elasticsearch·jenkins
武子康3 小时前
大数据-171 Elasticsearch ES-Head 与 Kibana 配置 使用 测试
大数据·elasticsearch·搜索引擎·flink·spark·全文检索·kylin
CtrlCV 攻城狮3 小时前
Elasticsearch是做什么的?
大数据·elasticsearch·搜索引擎