Mastering Elasticsearch(中文版)

update API

当往索引中添加新的文档到索引中时,底层的Lucene工具包会分析每个域,生成token流,token流过滤后就得到了倒排索引。在这个过程中,输入文本中一些不必要的信息会丢弃掉。这些不必要的信息可能是一些特殊词的位置(如果没有存储term vectors),一些停用词或者用同义词代替的词,或者词尾(抽取词干时)。这也是为什么无法对Lucene中的文档进行修改,每次需要修改一个文档时,就必须把文档的所有域添加到索引中。ElasticSearch通过使用_source这个代理域来存储和检索文档中的真实数据,以绕开前面的问题。当我们想更新文档时,ElasticSearch 会把数据存放在_source域中,然后做出修改,最后把更新后的文档添加到索引中。当然,前提是_source域的这项特性必须生效。关于update,非常重要的一个限制就是文档更新命令只能更新一个文档,基于查询命令的文档更新还没有正式开放出来。

如果读者对Apache Lucene 分析器的工作原理或者上面提到的术语不熟悉,请参考 第1章 ElasticSearch简介认识Apache Lucene 一节的内容。

从API的角度来看,在发送请求到某个具体文档并带上_update端点时,文档更新操作就会触发。比如,/library/book/1/_update。接下来看看用这项功能都能做些什么。
本节接下来的内容都会使用如下命令索引的文档为例,来演示update的相关特性。文档索引命令如下:

curl -XPUT localhost:9200/library/book/1 -d '{
"title": "The Complete Sherlock Holmes","author": "Arthur Conan
Doyle","year": 1936,"characters": ["Sherlock Holmes","Dr.
Watson", "G. Lestrade"],"tags": [],"copies": 0, "available" :
false, "section" : 12
}'

简单的域更新操作

第一个演示案例就是更改选定文档的某个域。例如,看如下的命令:

curl -XPOST localhost:9200/library/book/1/_update -d '{
    "doc" : {
        "title" : "The Complete Sherlock Holmes Book",
        "year" : 1935
    }
}'

在上面的命令中,我们修改了文档的两个域,title域和year域。当添加上面的文档到索引中时,ElasticSearch的回复内容如下:

{"ok":true,"_index":"library","_type":"book","_id":"1","_version"2}

接下来看看文档的域是否更新成功,执行如下的命令:

curl -XGET localhost:9200/library/book/1?pretty

命令的返回内容如下:

{
"_index" : "library",
"_type" : "book",
"_id" : "1",
"_version" : 2,
"exists" : true, "_source" : {"title":"The Complete Sherlock
    Holmes Book","author":"Arthur Conan
    Doyle","year":1935,"characters":["Sherlock Holmes","Dr.
    Watson","G.
    Lestrade"],"tags":[],"copies":0,"available":false,
    "section":12}
}

可以看到在_source域中,title域和year域已经被更新了。接下来进入到下一个例子中,该例使用了脚本。

使用脚本选择性更新域

有时,在修改文档过程中添加一些额外的判断逻辑会很有用,这也是ElasticSearch允许用户在update API中使用脚本的原因。比如,我们可以发送如下的请求:

curl localhost:9200/library/book/1/_update -d '{
    "script" : "if(ctx._source.year == start_date)ctx._source.year
        = new_date; else ctx._source.year = alt_date;",
    "params" : {
        "start_date" : 1935,
        "new_date" : 1936,
        "alt_date" : 1934
    }
}'

可以看到,script域定义了对命令中文档的操作方式。脚本可以按照需求自行定义。用户同样可以引用ctx变量来获取文档的域。通常情况下,用户还可以在脚本中定义其它的变量。使用ctx._source,用户可更新现有的域,也可以创建新的域(如果用到了不存在的域,ElasticSearch会创建新的域)。域的创建也是实实在在发生在上例ctx._source.year=new_date脚本中。用户还可以使用remove()方法删除文档的域,比如:

curl localhost:9200/library/book/1/_update -d '{
    "script" : "ctx._source.remove(\"year\");"
}'

使用update API创建和删除文档

Update API不仅可以修改文档的某个域,同时也能用于操纵整个文档。upsert特性使得在定位到一个不存在的文档时,它会被创建出来。参考如下的命令:

curl localhost:9200/library/book/1/_update -d '{
    "doc" : {
        "year" : 1900
    },
    "upsert" : {
        "title" : "Unknown Book"
    }
}'

如果文档(索引library中,type为book,id为1)存在,该命令将重置year域的值;否则文档将会被创建出来,新建的文档包含upset中定义的title域。当然,上面的命令还可以使用脚本,写成如下格式:

curl localhost:9200/library/book/1/_update -d '{
"script" : "ctx._source.year = 1900",
    "upsert" : {
        "title" : "Unknown Book"
    }
}'

Update API中最后一点有趣的特性就是允许用户选择性地删除整个文档。该功能可以通过在命令中设置ctx.op值为delete来实现。比如,下面的命令就实现在从索引中删除文档的功能:

curl localhost:9200/library/book/1/_update -d '{
    "script" : "ctx.op = \"delete\""
}'

当然,用户还可以使用脚本实现更复杂的逻辑来删除满足条件的文档。