服务端阅读 05月27日 23:53
Elasticsearch 更新和删除操作的底层原理是什么?
Elasticsearch 底层基于 Lucene,而 Lucene 的段(segment)是不可变的。这意味着已写入段的文档无法原地修改或删除。Elasticsearch 的更新和删除操作都建立在这一约束之上,通过标记删除 + 重新索引的方式实现,再由段合并完成物理清理。更新操作:标记删除 + 重新索引Elasticsearch 的更新并不是原地修改文档。当你更新一个文档时,实际发生的是两步操作:旧文档在 .del 文件中被标记为 deleted新文档被索引到一个新的段中也就是说,更新 = 删除旧版本 + 插入新版本。这是由倒排索引的不可变性决定的——段一旦写入就无法修改,只能追加。PUT /products/_doc/1{ "name": "MacBook Pro", "price": 14999, "updated_at": "2025-01-15"}上述请求如果文档 ID=1 已存在,旧文档会被标记删除,新文档写入新段。如果不指定 ID,则直接作为新文档插入。部分更新(Partial Update)全量替换需要发送完整文档,网络开销大。部分更新通过 _update API 只修改指定字段,但底层仍然是标记删除 + 重新索引——只是服务端帮你完成了合并旧文档和新字段的步骤:POST /products/_update/1{ "doc": { "price": 12999 }}脚本更新对于需要动态计算的场景,可以用脚本更新:POST /products/_update/1{ "script": { "source": "ctx._source.price += params.delta", "params": { "delta": 500 } }}upsert 操作当不确定文档是否存在时,upsert 可以在文档不存在时插入、存在时更新:POST /products/_update/1{ "doc": { "price": 12999 }, "upsert": { "name": "MacBook Pro", "price": 12999 }}删除操作:逻辑删除与段合并清理删除文档时,Elasticsearch 不会立即从磁盘移除数据。而是在 .del 文件中标记该文档为 deleted 状态。被标记的文档仍然存在于段中,但查询时会被过滤掉。DELETE /products/_doc/1物理删除何时发生?物理删除发生在段合并(segment merge)过程中。Lucene 后台会定期将多个小段合并为大段,此时被标记为 deleted 的文档不会被写入新段,从而实现真正的磁盘空间回收。你也可以手动触发合并清理:POST /products/_forcemerge?only_expunge_deletes=trueonly_expunge_deletes=true 表示只合并含有删除文档的段,不影响无删除标记的段。按条件批量删除对于需要按查询条件删除的场景,使用 delete_by_query:POST /products/_delete_by_query{ "query": { "range": { "price": { "lte": 100 } } }}注意:delete_by_query 是先扫描再逐个标记删除,大数量下耗时长,建议在低峰期执行并设置 wait_for_completion=false 异步执行。版本控制与乐观并发_version 字段每个文档都有一个 _version 字段,每次写操作(index、update、delete)都会使版本号递增。这用于防止旧版本覆盖新版本——如果一个更新请求基于的版本号已过期,操作会被拒绝。乐观并发控制Elasticsearch 使用 if_seq_no 和 if_primary_term 实现乐观并发控制(OCC)。在读取文档时获取当前的 seqno 和 primaryterm,更新时带上这两个值,如果文档已被其他操作修改(seq_no 已变),则返回 409 冲突:PUT /products/_doc/1?if_seq_no=5&if_primary_term=1{ "name": "MacBook Pro", "price": 13999}如果不做并发控制,两个请求同时更新同一文档,后到的请求会覆盖先到的结果——这在电商库存扣减等场景下是严重问题。近实时搜索与 refresh 机制文档写入后并不是立即可搜索。Elasticsearch 的写入流程是:文档先写入内存缓冲区(index buffer)同时写入 translog(事务日志,保证持久性)每隔 refresh_interval(默认 1s)执行一次 refresh,将内存缓冲区的数据写入新段,文档变为可搜索这意味着更新和删除操作也有近一秒的延迟才对搜索可见。生产环境中,可以适当调大 refresh_interval(如 30s)来提升写入吞吐量,代价是搜索可见延迟增加。性能优化要点更新场景:优先使用部分更新而非全量替换,减少网络传输和 _source 重写开销高频更新使用 Bulk API 批量提交避免在热索引上频繁单条更新,考虑异步队列聚合后批量写入删除场景:大批量删除用 delete_by_query 而非逐条 DELETE删除后若段膨胀明显,执行 force_merge 回收空间(只对只读索引执行,否则可能产生超大段)删除大量数据后关注磁盘水位,段合并需要额外磁盘空间通用建议:监控 GET /_nodes/stats/indexing 中的索引吞吐和删除计数调整 index.merge.policy 控制段合并策略和频率更新和删除都会产生 translog 和段碎片,定期评估索引是否需要 reindex面试中回答这个问题,核心要讲清楚三点:段不可变导致更新是删除+插入、删除是逻辑标记物理清理靠段合并、并发控制靠 seqno/primaryterm 实现乐观锁。理解这三层,就能应对追问。