【Elasticsearch从入门到精通】第15篇:Elasticsearch删除与更新API——精确操作与脚本更新
上一篇【第14篇】Elasticsearch文档检索API——GET、MGet与字段选择
下一篇【第16篇】Elasticsearch批量操作API——Bulk、Reindex与跨集群索引
摘要
数据的删除和更新是Elasticsearch文档操作中不可或缺的环节。本文全面讲解了Elasticsearch删除与更新API的使用方法,涵盖Delete API的单文档精确删除及响应码解析、Update API的三种核心用法——基于Painless脚本的灵活更新(ctx._source操作)、部分文档字段合并更新、upsert插入或更新的条件操作以及doc_as_upsert简写模式。同时深入介绍了Delete by Query查询删除和Update by Query查询更新的批量操作,包括切片(slices)并行处理策略、Task API追踪长时间操作进度、取消任务和动态调整速率等高级特性。掌握这些内容将使你能够灵活应对各种数据变更场景。
一、Delete API单文档删除
1.1 基本用法
Delete API(DELETE)允许根据文档ID从索引中删除JSON文档:
DELETE twitter/_doc/1
如果文档存在且被成功删除,响应如下:
{
"_index": "twitter",
"_type": "_doc",
"_id": "1",
"_version": 2,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
1.2 响应码说明
| 状态码 | 含义 | 说明 |
|---|---|---|
| 200 | 文档已找到并删除 | result为"deleted" |
| 404 | 文档不存在 | result为"not_found" |
| 409 | 版本冲突 | 并发操作时版本不匹配 |
注意:即使文档不存在,Delete API也会返回
_version递增。在Elasticsearch内部,旧文档只是被标记为已删除,并不会立即从磁盘中移除,后台的段合并过程会在适当时机清除这些标记数据。
1.3 带版本控制的删除
可以使用乐观并发控制来确保删除的是正确版本的文档:
DELETE twitter/_doc/1?if_seq_no=1&if_primary_term=1
如果序列号不匹配,操作将返回版本冲突错误。
二、Update API基本用法
2.1 工作原理
Update API(_update)允许根据提供的脚本或部分文档来更新已有文档。其内部流程为:
- 从索引中获取文档(Get操作)
- 运行脚本或合并部分文档(Modify操作)
- 将结果重新索引(Reindex操作)
因此,Update API本质上是一次"读取-修改-写入"的操作。它使用版本控制来确保在Get和Reindex期间没有发生其他更新。
注意:此操作仍然意味着文档的完全重新索引,只是减少了一些网络往返,并降低了Get和Reindex操作之间版本冲突的可能性。需要启用
_source才能使用此功能。
三、使用Painless脚本更新
3.1 Painless脚本基础
Painless是Elasticsearch默认的脚本语言,专门为Elasticsearch设计,具有高性能和安全性。在Update API中使用脚本可以实现灵活的更新逻辑。
3.2 增加计数器
以下脚本将 counter 字段的值增加指定数量:
POST twitter/_update/1
{
"script": {
"source": "ctx._source.counter += params.count",
"lang": "painless",
"params": {
"count": 4
}
}
}
3.3 向列表添加元素
向 tags 列表字段中添加标签(如果标签已存在,仍会被添加,因为这是列表操作):
POST twitter/_update/1
{
"script": {
"source": "ctx._source.tags.add(params.tag)",
"lang": "painless",
"params": {
"tag": "blue"
}
}
}
3.4 从列表删除元素
从标签列表中删除指定标签。需要先定位元素的位置再删除:
POST twitter/_update/1
{
"script": {
"source": "ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag))",
"lang": "painless",
"params": {
"tag": "blue"
}
}
}
3.5 添加和删除字段
添加新字段:
POST twitter/_update/1
{
"script": {
"source": "ctx._source.new_field = params.value",
"lang": "painless",
"params": {
"value": "hello"
}
}
}
删除字段:
POST twitter/_update/1
{
"script": {
"source": "ctx._source.remove('new_field')",
"lang": "painless"
}
}
3.6 条件操作
根据条件执行不同的操作。以下示例中,如果 tags 字段包含 green,则删除文档;否则不执行任何操作(noop):
POST twitter/_update/1
{
"script": {
"source": "if (ctx._source.tags.contains('green')) { ctx.op = 'delete'; } else { ctx.op = 'noop'; }",
"lang": "painless"
}
}
3.7 脚本中可用的上下文变量
除了 ctx._source 外,还可以通过 ctx 映射访问以下变量:
| 变量 | 说明 |
|---|---|
_index |
文档所属索引 |
_type |
文档类型 |
_id |
文档ID |
_version |
文档版本号 |
_routing |
路由值 |
_now |
当前时间戳 |
_op |
操作类型(update/noop/delete) |
四、部分字段更新(doc)
4.1 基本用法
Update API支持传递部分文档进行更新,在内部完成合并操作(简单的递归合并):
POST twitter/_update/1
{
"doc": {
"name": "new_name",
"age": 20
}
}
上述请求会将 name 和 age 字段与现有的 _source 合并,其他字段保持不变。
4.2 detect_noop避免无效更新
默认情况下,如果 doc 中的字段值与原文档完全相同,Elasticsearch会跳过更新,响应返回 "result": "noop":
POST twitter/_update/1
{
"doc": {
"name": "new_name"
}
}
如果 name 的值未变,响应为:
{
"_id": "1",
"result": "noop"
}
可以通过设置 "detect_noop": false 来禁用此行为,强制执行更新:
POST twitter/_update/1
{
"doc": {
"name": "new_name"
},
"detect_noop": false
}
注意:如果同时指定了
doc和script,则doc会被忽略。建议将部分文档字段放在脚本本身中。
五、upsert与doc_as_upsert
5.1 upsert操作
upsert 元素用于处理"文档不存在则插入,存在则更新"的场景:
POST twitter/_update/2
{
"script": {
"source": "ctx._source.counter += params.count",
"lang": "painless",
"params": {
"count": 1
}
},
"upsert": {
"counter": 1
}
}
首次执行(文档不存在):执行创建操作,"result": "created"。
再次执行(文档已存在):执行脚本更新,"result": "updated"。
5.2 scripted_upsert参数
如果不管文档是否存在都希望运行脚本,可以设置 scripted_upsert: true:
POST twitter/_update/2
{
"scripted_upsert": true,
"script": {
"source": "if (ctx._source.counter == null) { ctx._source.counter = 1; } else { ctx._source.counter += 1; }",
"lang": "painless"
},
"upsert": {}
}
5.3 doc_as_upsert参数
doc_as_upsert 是一种简写模式,当文档不存在时使用 doc 的内容作为新文档插入:
POST twitter/_update/2
{
"doc": {
"name": "kimchy",
"counter": 1
},
"doc_as_upsert": true
}
5.4 Update API三种模式对比
| 模式 | 参数 | 文档存在时 | 文档不存在时 | 适用场景 |
|---|---|---|---|---|
| 脚本更新 | script |
执行脚本 | 报错 | 复杂逻辑更新 |
| 部分更新 | doc |
合并字段 | 报错 | 简单字段更新 |
| 条件插入 | upsert |
执行脚本 | 插入upsert内容 | 计数器、状态管理 |
| 脚本插入 | scripted_upsert |
执行脚本 | 执行脚本 | 脚本统一处理初始化 |
| 文档插入 | doc_as_upsert |
合并字段 | 插入doc内容 | 简单的插入或更新 |
六、Delete by Query查询删除
6.1 基本用法
Delete by Query(_delete_by_query)对每个与查询匹配的文档执行删除操作:
POST twitter/_delete_by_query
{
"query": {
"match": {
"message": "some message"
}
}
}
响应如下:
{
"took": 438,
"timed_out": false,
"total": 119,
"deleted": 119,
"batches": 1,
"version_conflicts": 0,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1,
"throttled_until_millis": 0,
"failures": []
}
6.2 版本冲突处理
_delete_by_query 在启动时获取索引快照,使用内部版本控制删除匹配内容。如果文档在快照和删除请求之间被修改,会出现版本冲突。
通过设置 conflicts=proceed 可以让操作在版本冲突时继续执行:
POST twitter/_delete_by_query?conflicts=proceed
{
"query": {
"match_all": {}
}
}
6.3 多索引操作
可以同时对多个索引执行查询删除:
POST twitter,blog/_delete_by_query
{
"query": {
"match_all": {}
}
}
6.4 关键参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
refresh |
完成后刷新所有涉及分片 | 不刷新 |
scroll_size |
滚动批次大小 | 1000 |
requests_per_second |
限速(-1为不限速) | -1 |
wait_for_completion |
是否等待完成(false返回Task) | true |
timeout |
每个写请求的超时时间 | 1m |
scroll |
搜索上下文保持时间 | 5m |
slices |
切片数量(并行处理) | 1 |
6.5 切片并行处理
_delete_by_query支持切片滚动,使删除过程可以并行执行。
自动切片:
POST twitter/_delete_by_query?conflicts=proceed&slices=auto
{
"query": {
"match_all": {}
}
}
auto 将为每个分片使用一个切片。手动切片时,建议遵循以下准则:
- 切片数等于索引分片数时性能最佳
- 删除性能随切片数和可用资源线性扩展
- 切片数不要大于分片数量,否则会增加额外开销
七、Update by Query查询更新
7.1 基本用法
Update by Query(_update_by_query)对索引中的每个文档执行更新操作:
POST twitter/_update_by_query?conflicts=proceed
上述请求会重新索引所有文档,适用于获取新添加的映射字段。
7.2 带查询条件的更新
结合DSL查询,只更新匹配特定条件的文档:
POST twitter/_update_by_query
{
"query": {
"term": {
"user": "kimchy"
}
}
}
7.3 使用脚本更新
可以在更新中使用脚本修改文档内容:
POST twitter/_update_by_query
{
"query": {
"term": {
"user": "kimchy"
}
},
"script": {
"source": "ctx._source.likes++",
"lang": "painless"
}
}
7.4 脚本中的ctx.op控制
在 _update_by_query 的脚本中,可以设置 ctx.op 来控制对文档的操作:
| ctx.op值 | 行为 |
|---|---|
update(默认) |
更新文档 |
noop |
跳过该文档 |
delete |
删除该文档 |
7.5 获取新映射属性
_update_by_query 的一个重要应用场景是获取新添加的映射属性。例如:
PUT twitter/_mapping
{
"properties": {
"flag": {
"type": "keyword"
}
}
}
POST twitter/_update_by_query
这样所有文档都会被重新索引,新添加的 flag 字段会被提取出来。
八、Task API追踪操作进度
8.1 获取任务状态
对于长时间运行的查询删除或查询更新操作,可以使用Task API追踪进度:
GET _tasks?detailed=true&actions=*/delete/byquery
或者使用任务ID直接查询:
GET _tasks/r1A2WoRbTwKZ516z6NEs5A:36619
响应中的 status 字段包含任务的实际进度信息:
{
"completed": false,
"task": {
"node": "r1A2WoRbTwKZ516z6NEs5A",
"id": 36619,
"type": "transport",
"action": "indices:data/write/delete/byquery",
"status": {
"total": 119,
"deleted": 60,
"batches": 1,
"version_conflicts": 0,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
}
}
}
}
8.2 异步执行
设置 wait_for_completion=false,让操作在后台执行,立即返回任务ID:
POST twitter/_delete_by_query?wait_for_completion=false
{
"query": {
"match_all": {}
}
}
8.3 取消任务
使用取消任务API终止正在运行的删除/更新操作:
POST _tasks/r1A2WoRbTwKZ516z6NEs5A:36619/_cancel
8.4 动态调整速率
在运行时使用 _rethrottle API动态调整操作速率:
POST _delete_by_query/r1A2WoRbTwKZ516z6NEs5A:36619/_rethrottle?requests_per_second=-1
九、总结与最佳实践
9.1 核心要点回顾
- Delete API 提供精确的单文档删除能力,支持乐观并发控制
- Update API 支持三种模式:脚本更新(灵活复杂)、doc部分更新(简单直接)、upsert(条件插入)
- Painless脚本 是更新的核心工具,通过
ctx._source操作文档字段 - Delete/Update by Query 实现批量删除和更新,支持切片并行处理
- Task API 提供长时间操作的进度追踪、取消和速率调整能力
- detect_noop 和 conflicts=proceed 是优化更新性能的重要手段
9.2 生产环境最佳实践
- 优先使用doc部分更新:简单的字段修改优先使用
doc而非脚本,性能更好 - 合理使用upsert:计数器、状态管理等场景使用
upsert避免"先查后写"的竞态条件 - 批量操作切片化:大数据量的
_delete_by_query和_update_by_query务必使用切片并行 - 速率控制:在业务低峰期执行大批量删除/更新,并使用
requests_per_second限制对集群的影响 - 异步执行:大批量操作使用
wait_for_completion=false异步执行,通过Task API监控进度
上一篇【第14篇】Elasticsearch文档检索API——GET、MGet与字段选择
下一篇【第16篇】Elasticsearch批量操作API——Bulk、Reindex与跨集群索引
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)