# 局部更新

在《更新》一章中，我们讲到了要是想更新一个文档，那么就需要去取回数据，更改数据然后将整个文档进行重新索引。当然，你还可以通过使用`更新`API来做部分更新，比如增加一个计数器。

正如我们提到的，文档不能被修改，它们只能被替换掉。`更新`API也**必须**遵循这一法则。从表面看来，貌似是文档被替换了。对内而言，它必须按照*找回-修改-索引*的流程来进行操作与管理。不同之处在于这个流程是在一个片(shard) 中完成的，因此可以节省多个请求所带来的网络开销。除了节省了步骤，同时我们也能减少多个进程造成冲突的可能性。

使用`更新`请求最简单的一种用途就是添加新数据。新的数据会被合并到现有数据中，而如果存在相同的字段，就会被新的数据所替换。例如我们可以为我们的博客添加`tags`和`views`字段：

```javascript
POST /website/blog/1/_update
{
   "doc" : {
      "tags" : [ "testing" ],
      "views": 0
   }
}
```

如果请求成功，我们就会收到一个类似于`索引`时返回的内容:

```javascript
{
   "_index" :   "website",
   "_id" :      "1",
   "_type" :    "blog",
   "_version" : 3
}
```

再次取回数据，你可以在`_source`中看到更新的结果：

```javascript
{
   "_index":    "website",
   "_type":     "blog",
   "_id":       "1",
   "_version":  3,
   "found":     true,
   "_source": {
      "title":  "My first blog entry",
      "text":   "Starting to get the hang of this...",
      "tags": [ "testing" ], <1>
      "views":  0 <1>
   }
}
```

1. 新的数据已经添加到了字段`_source`中。

### 使用脚本进行更新

我们将会在《脚本》一章中学习更详细的内容，我们现在只需要了解一些在Elasticsearch中使用API无法直接完成的自定义行为。默认的脚本语言叫做MVEL，但是Elasticsearch也支持JavaScript, Groovy 以及 Python。

MVEL是一个简单高效的JAVA基础动态脚本语言，它的语法类似于Javascript。你可以在[Elasticsearch scripting docs](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-scripting.html) 以及 [MVEL website](http://mvel.codehaus.org/Getting+Started+for+2.0)了解更多关于MVEL的信息。

脚本语言可以在`更新`API中被用来修改`_source`中的内容，而它在脚本中被称为`ctx._source`。例如，我们可以使用脚本来增加博文中`views`的数字：

```javascript
POST /website/blog/1/_update
{
   "script" : "ctx._source.views+=1"
}
```

我们同样可以使用脚本在`tags`数组中添加新的tag。在这个例子中，我们把新的tag声明为一个变量，而不是将他写死在脚本中。这样Elasticsearch就可以重新使用这个脚本进行tag的添加，而不用再次重新编写脚本了：

```javascript
POST /website/blog/1/_update
{
   "script" : "ctx._source.tags+=new_tag",
   "params" : {
      "new_tag" : "search"
   }
}
```

获取文档，后两项发生了变化：

```javascript
{
   "_index":    "website",
   "_type":     "blog",
   "_id":       "1",
   "_version":  5,
   "found":     true,
   "_source": {
      "title":  "My first blog entry",
      "text":   "Starting to get the hang of this...",
      "tags":  ["testing", "search"], <1>
      "views":  1 <2>
   }
}
```

1. `tags`数组中出现了`search`。
2. `views`字段增加了。

我们甚至可以使用`ctx.op`来根据内容选择是否删除一个文档：

```javascript
POST /website/blog/1/_update
{
   "script" : "ctx.op = ctx._source.views == count ? 'delete' : 'none'",
    "params" : {
        "count": 1
    }
}
```

## 更新一篇可能不存在的文档

想象一下，我们可能需要在Elasticsearch中存储一个页面计数器。每次用户访问这个页面，我们就增加一下当前页面的计数器。但是如果这是个新的页面，我们不能确保这个计数器已经存在。如果我们试着去更新一个不存在的文档，更新操作就会失败。

为了防止上述情况的发生，我们可以使用`upsert`参数来设定文档不存在时，它应该被创建：

```javascript
POST /website/pageviews/1/_update
{
   "script" : "ctx._source.views+=1",
   "upsert": {
       "views": 1
   }
}
```

首次运行这个请求时，`upsert`的内容会被索引成新的文档，它将`views`字段初始化为`1`。当之后再请求时，文档已经存在，所以`脚本`更新就会被执行，`views`计数器就会增加。

## 更新和冲突

在本节的开篇我们提到了当*取回*与*重新索引*两个步骤间的时间越少，发生改变冲突的可能性就越小。但它并不能被完全消除，在`更新`的过程中还可能存在另一个进程进行重新索引的可能性。

为了避免丢失数据，`更新`API会在*获取*步骤中获取当前文档中的`_version`，然后将其传递给*重新索引*步骤中的`索引`请求。如果其他的进程在这两部之间修改了这个文档，那么`_version`就会不同，这样更新就会失败。

对于很多的局部更新来说，文档有没有发生变化实际上是不重要的。例如，两个进程都要增加页面浏览的计数器，谁先谁后其实并不重要 —— 发生冲突时只需要重新来过即可。

你可以通过设定`retry_on_conflict`参数来设置自动完成这项请求的次数，它的默认值是`0`。

```javascript
POST /website/pageviews/1/_update?retry_on_conflict=5 <1>
{
   "script" : "ctx._source.views+=1",
   "upsert": {
       "views": 0
   }
}
```

1. 失败前重新尝试5次

这个参数非常适用于类似于增加计数器这种无关顺序的请求，但是还有些情况的顺序就是**很重要**的。例如上一节提到的情况，你可以参考乐观并发控制以及悲观并发控制来设定文档的版本号。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hezhiqiang.gitbook.io/elasticsearch/data/partial_update.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
