全局更新
在 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
}
}