全局更新
在 Elasticsearch 中document是不可改变 的,不能修改它们。所以当我们使用更新API时,其实是经历了:
- 查询旧数据
- 标记旧数据为删除状态
- 插入新数据
这里并不是将旧文档直接删除,而是打上删除标记,是为了提升ES的性能。但是如果一直不删除旧文档则会越堆越多,所以当旧文档到达一定数量时,ES会做一次清理,物理删除掉这些被标记删除的文档。
全局更新的API其实就是PUT新增的API:
PUT /employee/_doc/1
{
"name":"xiaozhangsan",
"age":2,
"signature":"I'm a baby",
"hobby":["sugar","milk"]
}
这里将员工 zhangsan 全局更新为他的儿子 xiaozhangsan,返回结果:
{
"_index" : "employee",
"_type" : "_doc",
"_id" : "1",
"_version" : 5,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 9,
"_primary_term" : 2
}
- _index 修改文档所属的索引
- _type 修改文档所属的类型,7.x版本中只允许为 _doc
- _id 修改文档的id
- _version 当前版本号,可用于实现乐观锁机制
- _result 当前属于什么操作,这里是做的是全局更新所以是 updated
- _shards 对于分片的操作结果
部分更新
前面有说过,ES中的文档是无法修改的,所以局部更新的实现步骤和全局更新类似:
- 查询出旧文档的 _source 部分
- 将更新内容写入旧的 _source
- 删除旧文档(标记删除)
- 使用修改后的 _source 来新增一个文档
API如下:
POST /employee/_doc/1/_update
{
"doc":{
"age":3
}
}
这里由于 xiaozhangsan 长大了,所以只将其 age 字段加一,返回结果如下:
{
"_index" : "employee",
"_type" : "_doc",
"_id" : "1",
"_version" : 6,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 10,
"_primary_term" : 2
}
与全局更新的返回结果基本一样。
当然局部更新不仅限于更新字段值,也可以做字段新增
脚本局部更新
简单类型更新
在ES脚本中可以使用ctx._source
来访问文档的 _source,这里使用脚本再讲 xiaozhangsan 的年龄加一:
POST /employee/_doc/1/_update
{
"script":"ctx._source.age+=1"
}
对象类型更新
也可以在脚本中调用对象的方法,比如 hobby 就是一个数组对象,可以像这样给它添加数据:
POST /employee/_doc/1/_update
{
"script":"ctx._source.hobby.add('sleep')"
}
使用参数更新
也可以使用参数代替硬编码,像这样:
POST /employee/_doc/1/_update
{
"script": {
"source": "ctx._source.hobby.add(params.hobby)",
"params": {
"hobby": "sleep"
}
}
}
新增字段
注意字符串的值需要加单引号:
POST /employee/_doc/1/_update
{
"script": "ctx._source.sex='male'"
}
删除字段
POST /employee/_doc/1/_update
{
"script": "ctx._source.remove('sex')"
}
根据条件更新
使用 if ,如果文档hobby中包含 milk 则删除文档,否则不做任何操作:
POST /employee/_doc/2/_update
{
"script": {
"source":"if(ctx._source.hobby.contains('milk')){ctx.op='delete'}else{ctx.op='none'}"
}
}
更新默认值
使用 upsert 设值字段默认值,当 age 不存在时会新增它并设置为1:
POST /employee/_doc/1/_update
{
"script":"ctx._source.age+=1",
"upsert": {
"age":1
}
}
冲突和重试
由于ES会在更新数据时自动使用 _version 字段来做乐观锁,所以更新操作可能由于并发冲突导致失败,ES也为我们提供了 retry_on_conflict 实现重试机制,可用于计数器增加、年龄增加这样的对于并发顺序无要求的并发冲突问题:
POST /employee/_doc/1/_update?retry_on_conflict=5
{
"script":"ctx._source.age+=1",
"upsert": {
"age":1
}
}