目录

  • 前言
  • Update API
  • 使用部分文档进行更新
  • Updata by Query API


前言

Elasticsearch操作中,最常用的就是搜索和更新操作了。之前介绍了Elasticsearch的搜索API,所以,今天我们来介绍下Elasticsearch的更新操作。

Update API

Elasticsearch的更新API为Update,在url中为_update方法,Update方法支持脚本更新和内容更新

更新API允许基于提供的脚本更新文档。 该操作从索引获取文档(与分片并置),运行脚本(使用可选的脚本语言和参数),并对结果进行索引(也允许删除或忽略操作)。 它使用版本控制来确保在“get”和“reindex”期间没有发生更新。

请注意,此操作仍然意味着文档的完全重新索引,它只是删除了一些网络往返,并减少了get和index之间版本冲突的可能性。 需要启用_source字段才能使此功能正常工作。

例如,让我们索引一个简单的文档:

PUT test/_doc/1
{
    "counter" : 1,
    "tags" : ["red"]
}

下面我们就以这个文档为例子进行一些常用的更新操作

脚本更新

现在,我们可以执行一个增加计数器counter的脚本:

POST test/_doc/1/_update
{
    "script" : {
        "source": "ctx._source.counter += params.count",
        "lang": "painless",
        "params" : {
            "count" : 4
        }
    }
}

我们可以在标签列表(tags)中添加一个标签(注意,如果标签存在,它仍会添加它,因为它是一个列表):

POST test/_doc/1/_update
{
    "script" : {
        "source": "ctx._source.tags.add(params.tag)",
        "lang": "painless",
        "params" : {
            "tag" : "blue"
        }
    }
}

注:ctx._source用于表示当前文档,后面接文档属性,即可进行文档属性的修改,如上面的将counter增加,添加tags等操作

除了_source ,下面的参数也存在在 cxt map中:_index,_type,_id,_version,_routing和_now(当前时间戳)

在文档中添加新的字段:

POST test/_doc/1/_update
{
    "script" : "ctx._source.new_field = 'value_of_new_field'"
}

new_field为新字段的名称
value_of_new_field为字段的初始值

删除一个字段:

POST test/_doc/1/_update
{
    "script" : "ctx._source.remove('new_field')"
}

除了简单的更新,我们还可以使用脚本进行一些复杂的更新操作,如下面的例子,如果tags字段包含green,此示例将删除doc,否则它不执行任何操作(noop)

POST test/_doc/1/_update
{
    "script" : {
        "source": "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'none' }",
        "lang": "painless",
        "params" : {
            "tag" : "green"
        }
    }
}

ctx.op=‘delete‘表示删除文档中的数据
ctx.op = 'none’表示不执行任何操作

使用部分文档进行更新

除了使用脚本进行更新,update还支持使用部分文档内容进行更新。
更新API支持传递部分文档,该部分文档将合并到现有文档中。 要完全替换现有文档,应使用index API。 以下为更新文档中的某个字段值:

POST test/_doc/1/_update
{
    "doc" : {
        "name" : "new_name"
    }
}

注:如果文档没有name这个字段,那么会先添加name字段,然后将new_name赋值进去

多个字段更新

{
    
      "doc" : {
        "new_field " : "new",
        "name":"m"
    }

doc相当于重新定义文档,执行了doc之后,文档内的数据就会变成doc中定义的数据,所以我们可以直接在doc更新和添加数据

注:如果同时指定了doc和script,则会忽略doc。

默认情况下,不更改任何内容的更新会检测到它们没有更改任何内容并返回“result”:“noop”,如下所示:

POST test/_doc/1/_update
{
    "doc" : {
        "name" : "new_name"
    }
}

如果在发送请求之前name是new_name,则忽略整个更新请求。 如果忽略请求,响应中的result元素将返回noop。

{
   "_shards": {
        "total": 0,
        "successful": 0,
        "failed": 0
   },
   "_index": "test",
   "_type": "_doc",
   "_id": "1",
   "_version": 6,
   "result": "noop"
}

参数

更新操作支持以下查询字符串参数:

retry_on_conflict	在更新的get和indexing阶段之间,另一个进程可能已经更新了同一文档。 默认情况下,更新将因版本冲突异常而失败。 retry_on_conflict	参数控制在最终抛出异常之前重试更新的次数。

routing		路由用于将更新请求路由到正确的分片,并在更新的文档不存在时为upsert请求设置路由。 不能用于更新现有文档的路由。

timeout		超时等待碎片变为可用。

wait_for_active_shards	在继续更新操作之前需要处于活动状态的分片副本数。 详情请见此处。

refresh		控制何时此请求所做的更改对搜索可见。 看?refresh。

_source		允许控制是否以及如何在响应中返回更新的源。 默认情况下,不会返回更新的源。 请参阅源过滤了解详细信息

version		更新API在内部使用Elasticsearch的版本控制支持,以确保在更新期间文档不会更改。 您可以使用
version参数指定仅在文档版本与指定版本匹配时才更新文档。



Updata by Query API

Updata API是对单个文档进行更新操作,很多时候,我们需要对多个文档进行更新操作,这时候就要使用 Updata by Query API了。 Updata by Query API会对符合条件的所有文档都进行更新

Updata by Query API对应的方法为_update_by_query

_update_by_query的最简单用法只是对索引中的每个文档执行更新而不更改源。 这对于获取新属性或其他一些在线映射更改很有用。

例:

POST twitter/_update_by_query?conflicts=proceed

类似结果:

{
  "took" : 147,
  "timed_out": false,
  "updated": 120,
  "deleted": 0,
  "batches": 1,
  "version_conflicts": 0,
  "noops": 0,
  "retries": {
    "bulk": 0,
    "search": 0
  },
  "throttled_millis": 0,
  "requests_per_second": -1.0,
  "throttled_until_millis": 0,
  "total": 120,
  "failures" : [ ]
}

_update_by_query在索引启动时获取索引的快照,并使用内部版本控制索引它的内容。 这意味着如果文档在拍摄快照的时间和处理索引请求之间发生更改,则会出现版本冲突。 当版本匹配时,文档会更新,版本号会递增。

备注:由于内部版本控制不支持将值0作为有效版本号,因此无法使用_update_by_query更新版本等于零的文档,并且将使请求失败。

所有更新和查询失败都会导致_update_by_query中止,并在响应失败时返回。 已执行的更新仍然存在。 换句话说,该过程不会回滚,只会中止。 当第一个故障导致中止时,失败的批量请求返回的所有故障都将在failure元素中返回; 因此,可能存在相当多的失败实体。

如果您只想简单地计算版本冲突,不要导致_update_by_query中止,您可以设置在URL中设置 conflicts=proceed 或在请求体中设置"conflicts": “proceed”。,上面例子中就使用了该参数。

我们可以使用查询DSL限制_update_by_query的更新范围

如下,_update_by_query只会更新user为kimchy的文档,而不是所有文档

POST twitter/_update_by_query?conflicts=proceed
{
  "query": {                   // 1
    "term": {
      "user": "kimchy"
    }
  }
}

_update_by_query同样支持脚本来更新文档

如下,我们将user为kimchy的所有文档的likes都加1

POST twitter/_update_by_query
{
  "script": {
    "source": "ctx._source.likes++",
    "lang": "painless"
  },
  "query": {
    "term": {
      "user": "kimchy"
    }
  }
}

_update_by_query 和_update使用差不多,只是_update_by_query可以通过DSL查询来指定我们更新的范围

例:

通过 script 的方法来为我们的 某个索引下的所有文档增加一个新的字段,比如:

POST twitter/_update_by_query
{
  "script": {
    "source": "ctx._source['contact'] = \"139111111111\""
  }
}

通过上面的方法,我们twitter索引中的所有的文档都添加一个新的字段 contact,并赋予它一个同样的值