更新的内部机制
注意:实际使用 murmurhash 算法
注意:更新任何一个字段都是全部删除。并发更新操作之间无事务隔离保证,会产生数据错位问题。
更新操作
1、单条覆盖更新
1、覆盖式更新,由客户端完成所有数据的组装,服务端认可数据的完整性,执行覆盖。
2、数据更新一次,内部会先删除,再插入。
3、数据总条数增加,直到下一次物理文件合并才会恢复正常统计。
PUT demo-000001
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"refresh_interval": "30s"
}
}
number_of_shards:指定索引对应的主分片的个数。默认 1。该参数只能在创建索引时指定。每个索引最多有 1024 个分片。可以通过指定 export ES_JAVA_OPTS="-Des.index.max_number_of_shards=128"
系统变量来修改该限制。
number_of_replicas:指定每个主分片对应的副本分片的个数。默认 1。
refresh_interval:控制刷新的时间间隔。刷新可以使索引的最近变化对于搜索可见,默认 1s。可以设置为 -1,表示禁止刷新。
更多索引参数可以参考 Elasticsearch官方的索引参数介绍
插入 id 为 1 的文档
PUT demo-000001/_doc/1
{
"name": "tom"
}
紧接着在 30 秒之内进行搜索,却发现没有搜索结果
GET demo-000001/_search
然后执行刷新操作
POST demo-000001/_refresh
接着进行搜索,却发现有了搜索结果
GET demo-000001/_search
接着插入 id 为 1 的文档,但是文档内容不同。
PUT demo-000001/_doc/1
{
"content": "hello world"
}
接着进行搜索,却发现原来 id 为 1 的文档的内容被覆盖了。
2、单条局部更新
1、局部更新本质也是覆盖式更新,只是由服务端完成数据的合并。
2、支持 upsert 与 script 更新。
3、一定要使用 POST,而不能使用 PUT。
接着对 id 为 1 的文档执行局部更新操作,增加一个 “title” 字段。
POST demo-000001/_update/1
{
"doc": {
"title": "how to do it"
}
}
接着进行搜索,发现文档原来的 “content” 字段保留了下来,此时文档中有 “title”、“content” 两个字段。
GET demo-000001/_search
测试一下,如果先删除 id 为 1 的文档,再执行局部更新操作。
DELETE demo-000001/_doc/1
同样地,执行局部更新操作,却出现 “document_missing_exception” 异常
POST demo-000001/_update/1
{
"doc": {
"title": "how to do it"
}
}
对此需要加 doc_as_upsert,如果指定文档不存在就进行插入。
POST demo-000001/_update/1
{
"doc": {
"title": "how to do it"
},
"doc_as_upsert": true
}
再来试一下脚本方式的局部更新。
首先插入一条 id 为 2 的文档。
PUT demo-000001/_doc/2
{
"content": "hi world",
"income": 100
}
接着对这条 id 为 2 的文档执行脚本局部更新。
POST demo-000001/_update/2
{
"script": {
"source": """
ctx._source.income += 200
"""
}
}
搜索发现,id 为 2 的文档的 income 的值变为了 300,并且 content 字段也保留了下来。
GET demo-000001/_search
再试一下使用脚本删除字段。
POST demo-000001/_update/2
{
"script": {
"source": """
if (ctx._source.containsKey('content')) {
ctx._source.remove('content')
}
"""
}
}
如果使用脚本方式进行更新,但是指定 id 的文档事先不存在,就需要使用 upsert、scripted_upsert 参数分别指定指定参数的默认值、如果脚本方式操作的文档不存在时是否进行插入。
POST demo-000001/_update/3
{
"script": {
"source": """
ctx._source.income += params.p1
""",
"params": {
"p1": 100
}
},
"upsert": {
"income": 100
},
"scripted_upsert": true
}
3、批量更新
1、海量数据更新优先选择 bulk。
2、支持插入式更新。
3、单条更新实际内部也是 bulk 执行。
4、实际是局部更新。
POST _bulk
{"update":{"_id":"1","_index":"book"}}
{"doc":{"content":"hello world","title":"this is title"},"doc_as_upsert":true}
{"update":{"_id":"1","_index":"book"}}
{"doc":{"content":"hi world"}}
4、条件更新
1、内部实现基于快照查询机制。
2、内部异步完成。
首先创建 kibana_sample_data_logs_update 索引
POST _reindex
{
"source": {
"index": "kibana_sample_data_logs"
},
"dest": {
"index": "kibana_sample_data_logs_update"
}
}
接着执行条件更新
POST kibana_sample_data_logs_update/_update_by_query
{
"query": {
"match_all": {}
},
"script": """
ctx._source.host = 'www.baidu.com'
"""
}
并发冲突
因为 Elasticsearch 不支持严格的 ACID 事务特性,并且更新过程包括:"查询-标记删除-插入"三个操作。
查询时添加 version 请求参数,并且设置为 true,可以在响应体中返回 version 版本号。
GET demo-000001/_search?version=true
更新文档时,可以使用 version_type 请求参数指定一个外部版本号。
PUT demo-000001/_doc/4?version=11&version_type=external
{
"title": "this is forth document"
}
如果 version_type 请求参数为 external,则下一次更新时就需要指定大于文档当前的版本号才会成功执行。
PUT demo-000001/_doc/4?version=20&version_type=external
{
"title": "this is forth document"
}
如果 version_type 请求参数为 external_gte,则下一次更新时就需要指定大于等于文档当前的版本号才会成功执行。
PUT demo-000001/_doc/4?version=20&version_type=external_gte
{
"title": "this is my forth document"
}