Elasticsearch在日常使用中,随着业务数据不断写入,索引里会积累大量过期、无效或需要清理的文档。如果只是删掉某几个文档,用deleteAPI指定文档ID就行;但如果要删除的是满足某个条件的一大批数据,比如删除所有状态为已作废的订单记录,逐条调用删除接口显然不现实。这时候就要用到_delete_by_query这个API。它允许我们写一条查询语句,Elasticsearch会自动匹配出所有符合条件的文档并批量删除。本文会从基本用法、关键参数、常见报错和生产环境实践几个方面,把这个API讲透。

delete_by_query基本语法与工作原理
_delete_by_query的最简用法只需要指定索引名和一个查询条件。请求格式如下:
POST /order_index/_delete_by_query
{
"query": {
"term": {
"status": "invalid"
}
}
}这条命令会删除order_index索引中所有status字段值为invalid的文档。query部分支持Elasticsearch所有的查询语法,包括bool组合查询、range范围查询、match全文检索等,写法上和普通的_search接口完全一致,上手没有任何额外学习成本。
它的工作原理值得了解:_delete_by_query并不是在Lucene层面直接批量标记删除,而是内部先执行一次搜索,获取所有匹配文档的ID,然后对每个文档执行批量删除请求(内部按批次走Bulk API)。整个过程一共分三个步骤:第一步获取索引快照,第二步基于该快照滚动拉取匹配的文档ID,第三步按批次发送删除请求。这也就解释了为什么它的执行效率比直接删除整个索引低得多,本质上它是搜索加逐批删除的组合操作。
响应结果中几个字段需要关注:total表示匹配到的文档总数,deleted表示实际删除的数量,version_conflicts表示版本冲突次数,failures数组里则是执行失败的明细。如果failures为空且deleted等于total,说明删除任务圆满完成。
关键参数详解:conflicts、wait_for_completion与batch_size
默认情况下,只要有文档在删除过程中被其他请求并发修改,产生了版本冲突,整个删除任务就会中止,并且冲突发生前已删除的文档不会回滚。这在有并发写入的索引上几乎是必然遇到的问题。解决办法是加上conflicts参数:
POST /order_index/_delete_by_query?conflicts=proceed
{
"query": {
"range": {
"create_time": {
"lt": "2023-01-01"
}
}
}
}conflicts=proceed的含义是遇到版本冲突时继续执行而不是中止任务。生产环境中,只要不是强一致性的删除场景,都建议带上这个参数,否则任务很容易执行到一半就停了,还需要自己判断哪些删了哪些没删。
另一个重要参数是wait_for_completion。默认值为true,表示HTTP请求会一直阻塞直到删除完成。对于大索引来说,删除可能耗时几分钟甚至几小时,HTTP连接很容易超时断开。设置为false后,请求会立即返回一个taskID,删除任务在后台异步执行,之后可以通过任务API查询进度:
# 发起异步删除任务
POST /order_index/_delete_by_query?conflicts=proceed&wait_for_completion=false
{
"query": { "term": { "status": "invalid" } }
}
# 返回结果中的task形如:oTUltX4IQMOUUVeiohTt8A:12345
# 用它查询任务状态
GET /_tasks/oTUltX4IQMOUUVeiohTt8A:12345
# 任务跑偏了想取消也可以
POST /_tasks/oTUltX4IQMOUUVeiohTt8A:12345/_cancel还有一个scroll_size参数控制每批处理的文档数量,默认1000。如果索引文档很小、机器资源充足,可以适当调大到5000提升吞吐;反之如果删除过程导致集群响应变慢,就应该调小,减轻单批压力。此外requests_per_second参数可以限速,设置为-1表示不限速,设置成正数则限制每秒执行的请求数,这在业务高峰期做数据清理时非常实用。
常见报错与避坑指南
使用_delete_by_query最常见的报错是version_conflict_engine_exception,前面提到的conflicts=proceed就能解决。第二种常见问题是集群写入阻塞:当磁盘使用率超过水位线(默认85%)时,Elasticsearch会给索引加上只读锁,此时任何写操作包括删除都会失败,报错信息里会提示FORBIDDEN/12/index read-only / allow delete。解决办法是清理磁盘空间后手动解除锁:
# 解除索引只读限制
PUT /order_index/_settings
{
"index.blocks.read_only_allow_delete": null
}第三个坑是大索引一次性删除对集群的冲击。假设索引有一亿条文档,条件命中了其中五千万条,一口气执行删除会带来两个问题:一是删除过程产生大量段文件标记,触发猛烈的段合并,段合并是典型的IO和CPU密集操作,可能把集群负载瞬间拉高;二是删除期间集群整体响应变慢,影响正常业务查询。稳妥的做法是分批执行,按时间范围或分片维度切分任务,比如每天的数据单独跑一次删除,或者利用requests_per_second限速慢慢删。
第四个坑是权限和安全性问题。_delete_by_query是危险操作,条件写错就可能误删大量数据,而Elasticsearch删除的数据是不可恢复的(除非有快照)。强烈建议操作前先用同样的查询条件执行一次_count,确认命中的文档数量和范围符合预期,再执行删除。重要索引还应该提前做好快照备份,给自己留一条退路。
delete_by_query与其他删除方式的对比选择
删除Elasticsearch数据其实有多种方式,各有适用场景。直接DELETE /索引名删整个索引速度最快,几乎瞬间完成,但会连_mapping和别名设置一起删掉,适合整索引废弃的场景。_delete_by_query适合删除索引中的一部分数据,灵活但相对较慢。如果按时间分索引存储(比如按天或按月的索引),直接删除过期索引比用_delete_by_query高效得多,这也是为什么日志类系统普遍推荐按时间划分索引并配合ILM索引生命周期管理自动滚动删除。
| 删除方式 | 适用场景 | 性能 | 是否影响mapping |
|---|---|---|---|
| DELETE /索引名 | 整个索引废弃 | 极快 | 是,索引结构一并删除 |
| _delete_by_query | 删除索引内部分数据 | 较慢,随数据量增长 | 否 |
| delete指定ID | 删除单个或少量文档 | 快 | 否 |
| 删除过期时间索引+ILM | 日志类周期性数据清理 | 快 | 是,但索引会自动重建 |
另外要注意_delete_by_query和_update_by_query的关系,两者机制完全一样,只是一个执行删除一个执行更新。如果你的需求其实是把无效数据打上标记而不是物理删除,用_update_by_query配合script会更合适,既保留了数据可追溯性,又能通过查询条件过滤掉标记数据。
总结一下:_delete_by_query是一个强大但需要谨慎使用的工具。生产环境操作的推荐姿势是:先_count验证条件、做快照备份、加上conflicts=proceed、大任务用wait_for_completion=false异步执行并限速、错峰分批操作。把这些细节都注意到,条件删除就能用得又稳又安全。
delete_by_queryElasticsearch删除数据ES条件删除修改时间:2026-09-06 06:18:40