上一篇【第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)允许根据提供的脚本或部分文档来更新已有文档。其内部流程为:

  1. 从索引中获取文档(Get操作)
  2. 运行脚本或合并部分文档(Modify操作)
  3. 将结果重新索引(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
  }
}

上述请求会将 nameage 字段与现有的 _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
}

注意:如果同时指定了 docscript,则 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 核心要点回顾

  1. Delete API 提供精确的单文档删除能力,支持乐观并发控制
  2. Update API 支持三种模式:脚本更新(灵活复杂)、doc部分更新(简单直接)、upsert(条件插入)
  3. Painless脚本 是更新的核心工具,通过 ctx._source 操作文档字段
  4. Delete/Update by Query 实现批量删除和更新,支持切片并行处理
  5. Task API 提供长时间操作的进度追踪、取消和速率调整能力
  6. detect_noopconflicts=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与跨集群索引


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐