Update API



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



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



e.g.索引一个简单的文档:



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



脚本更新



现在,执行一个增加计数器的脚本:


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


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


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

此外_source以下变量通过提供ctx地图:_index , _type , _id , _version , _routing 和 _now(当前时间戳)。



我们还可以在文档中添加一个新字段:


POST test/_doc/1/_update
{
    "script" : "ctx._source.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"
        }
    }
}


响应结果如下:



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


执行删除操作时,返回结果result为deleted。




部分文档更新



Update API还支持传递部分文档,该部分文档将合并到现有文档中(简单的递归合并,对象的内部合并,替换核心“键/值”和数组)。要完全替换现有文档,应使用index API。以下部分更新会向现有文档添加新字段:


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



如果同时doc和script指定,然后doc被忽略。最好是将部分文档的字段对放在脚本本身中。



但在实际操作中,如果同时指定doc和script,则会报错:



"type": "action_request_validation_exception",



"reason": " can't provide both script and doc;"



官方文档(2018-07-11)有误。




检查noop更新



如果doc指定,则其值与现有值合并_source。默认情况下,不更改任何内容的更新会检测到它们没有更改任何内容并返回“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"
}


您可以通过设置“detect_noop”来禁用此行为:false,如下所示:


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


区别:



禁用此行为后,不更改任何内容的更新也会返回updated并且文档版本号加1;



不禁用此行为,不更改任何内容的更新会返回noop并且文档版本号不变。




Upserts



如果文档尚不存在,则upsert元素的内容将作为新文档插入。如果文档存在,那么script将执行:


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


scripted_upsert



如果无论文档是否存在您都希望脚本运行,即脚本处理初始化文档而不是upsert元素,设置scripted_upsert为true:



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


注意:设置“scripted_upsert”为true后,如果文档3不存在,则会先创建文档3,将upsert元素的内容插入文档3,然后运行脚本script内容,即上述代码响应结果为(文档3起先并不存在):


{
  "_index": "test",
  "_type": "_doc",
  "_id": "3",
  "_version": 4,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 3,
  "_primary_term": 1
}


查询文档3:GET test/_doc/3



响应结果为:



{
  "_index": "test",
  "_type": "_doc",
  "_id": "3",
  "_version": 1,
  "found": true,
  "_source": {
    "counter": 5
  }
}


doc_as_upsert



同scripted_upsert,如果无论文档是否存在您都希望脚本运行,即脚本处理初始化文档而不是upsert元素,设置doc_as_upsert为true(文档4不存在):



POST test/_doc/4/_update
{
  "doc":{
    "name":"doc_name"
  },
  "doc_as_upsert":true,
  "upsert":{
    "name":"upsert_name"
  }
}


上述代码响应结果为:


{
  "_index": "test",
  "_type": "_doc",
  "_id": "4",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 42,
  "_primary_term": 1
}


查看文档4:GET test/_doc/4



响应结果:



{
  "_index": "test",
  "_type": "_doc",
  "_id": "4",
  "_version": 1,
  "found": true,
  "_source": {
    "name": "doc_name"
  }
}


如果没有doc_as_upsert为true这个设置,则文档4的内容为“upsert_name”。




参数编辑



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



retry_on_conflict



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



routing



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



timeout



超时等待碎片变为可用。



wait_for_active_shards



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



refresh



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



_source



允许控制是否以及如何在响应中返回更新的源。默认情况下,不会返回更新的源。详情source filtering请见。



version



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



注意:更新API不支持内部版本以外的版本控制



更新API不支持外部(版本类型external&external_gte)或强制(版本类型force)版本控制,因为它会导致Elasticsearch版本号与外部系统不同步。请改用 indexAPI。