4.2 读取、更新与删除:单文档生命周期


文档摘要

4.2 读取、更新、删除:单文档生命周期 本节摘要:写入之后的单文档操作有四件套:按 id 取回(Get)、存在性探针(HEAD)、局部更新(Update)、删除(Delete)。本节的反直觉核心是——更新从来不是原地改字段,而是"取旧文档、合并修改、整条重写";删除也只是打标记。理解这个模型,更新接口的参数、脚本更新的写法、磁盘回收的滞后就都顺理成章。 读取的三种姿势 姿势一:按 id 整条取回 请求走路由公式直达 1 号分片,读的是 source——写入时原样保存的 JSON 原文。点查不经过倒排表,也不依赖 refresh,所以"写入后立刻按 id 取"永远找得到,这与 4.1 的提醒并不矛盾:可见性分两层,按 id 取看存储层,按词搜看索引层。

4.2 读取、更新、删除:单文档生命周期

本节摘要:写入之后的单文档操作有四件套:按 id 取回(Get)、存在性探针(HEAD)、局部更新(Update)、删除(Delete)。本节的反直觉核心是——更新从来不是原地改字段,而是"取旧文档、合并修改、整条重写";删除也只是打标记。理解这个模型,更新接口的参数、脚本更新的写法、磁盘回收的滞后就都顺理成章。

读取的三种姿势

姿势一:按 id 整条取回

GET tickets/_doc/doc-1001
{ "_index": "tickets", "_id": "doc-1001", "found": true, "_source": { "title": "用户申请退款,处理速度太慢", "status": "pending", "priority": 2 } }

请求走路由公式直达 1 号分片,读的是 _source——写入时原样保存的 JSON 原文。点查不经过倒排表,也不依赖 refresh,所以"写入后立刻按 id 取"永远找得到,这与 4.1 的提醒并不矛盾:可见性分两层,按 id 取看存储层,按词搜看索引层。

姿势二:只取要用的字段

列表页只要标题与状态,整条 _source 白白多传几 KB。源过滤参数让响应瘦身:

GET tickets/_doc/doc-1001?_source=title,status # 返回的 _source 只剩两个字段

配合路由参数还能少走弯路:已知文档属于某客户时带上 routing,协调节点免查别处。高频点查场景里,这两个小参数积少成多。

姿势三:存在性探针

HEAD tickets/_doc/doc-1001 # 响应无body 只有状态码 200存在 404不存在

HEAD 请求不回传文档,只问在不在。轮询等待、缓存判空这类场景,省下的是真实带宽。

更新:取旧、合并、重写

反直觉的真相

引擎内部没有"改某个字段"的操作。所谓更新,是目标分片先取出旧文档的 _source,把修改合并进去,再按旧 id 整条重写,同时给旧版本打删除标记。代价模型由此确定:更新一条大文档的成本约等于重写它,与改动的字段大小无关。工单带三 KB 的回复历史,改一个状态字段也是三 KB 的重写——高频小改的负载评估必须按此计算。

局部更新请求

POST tickets/_update/doc-1001 { "doc": { "status": "resolved", "resolved_at": "2026-08-21T09:30:00Z" } }

doc 参数给出要合并的字段,其余字段原样保留。若此刻文档不存在,这条请求会报文档缺失——想要"有则改、无则建",加上 upsert 参数:

POST tickets/_update/doc-1002 { "doc": { "status": "pending" }, "upsert": { "title": "退款已到账", "status": "pending", "priority": 3 } }

文档不存在时按 upsert 的内容新建;存在时走 doc 合并。同步场景(上游可能乱序到达)几乎总该带它。

脚本更新:让引擎算

要做的修改依赖旧值时——计数器累加、数组去重追加——用脚本参数。脚本语言是引擎内置的语法简化版:

POST tickets/_update/doc-1001 { "script": { "source": "ctx._source.reply_count += params.n", "params": { "n": 1 } } }

ctx._source 指向旧文档原文,params 传外部参数。把数值写进脚本字符串是坏习惯(既绕过缓存又埋注入隐患),参数化是纪律。脚本在数据节点上执行,每次更新都是一次"取、算、写",密集脚本更新是集群 CPU 的常见热点,见 9.3 的诊断。

一次状态流转的完整过程

背景:工单流转服务要把 doc-1001 从待处理改为已解决,并给处理时长字段累计 36 小时;同时另一个自动挂起任务也可能改这条文档,两者几乎同时触发。操作:流转服务发脚本更新,累加时长并置状态;请求不指定版本条件。结果:两次更新先后到达,后到者胜,先到者的状态修改被覆盖——工单状态又变回待处理,监控告警。解读:更新接口默认"最后写入胜出",并发场景没有护栏;覆盖不是数据损坏(每次都是完整重写),但业务语义错了。修复与变式:给更新请求带上 4.3 的序列号条件,只有持有最新版本的一方被受理;或者把这类状态流转改为消息队列串行化,从源头消灭并发。

删除:打标记的艺术

DELETE tickets/_doc/doc-1001 # result: deleted 立即应答

删除应答很快,但磁盘不会立刻缩小。分片只在倒排结构里写入一个删除标记(墓碑),查询时过滤命中,真正的空间回收要等后台的段合并把带墓碑的旧段与活跃段合成新段。所以频繁写删的索引会观察到"删除了几百万条,磁盘纹丝不动"——不是泄漏,是合并还没轮到。8.3 讲段合并时会再回到这里。

操作 走不走倒排 依赖 refresh 吗 典型延迟
Get 按 id 取 否 读 _source 毫秒级
HEAD 探针 毫秒级
Update 局部更新 是(重写建索引) 重写部分依赖 毫秒到几十毫秒
Delete 删除 是(打墓碑) 否,墓碑即时生效 毫秒级

⚠️ 常见坑:把更新接口当数据库的行级更新用,高频改单字段大文档(例如每分钟更新带长回复历史的工单),写入吞吐与段膨胀双双恶化。热数据的小状态分离到独立小索引或内存结构,批量定时回写,是常见的解法。

考核与自测

考核点 达标标准
可见性两层 解释按 id 取不等刷新、按词搜要等的根源差别
更新模型 说出取旧、合并、重写三步,以及成本与文档大小成正比
upsert 时机 说明乱序同步场景为什么几乎总该带它
脚本纪律 参数化书写脚本,说出字符串拼接的两处风险

四件套收尾

  • Get 读 _source 不依赖 refresh,按 id 点查永远即时;要瘦身用源过滤,要省流量用 HEAD。
  • 更新是"取旧、合并、整条重写",成本与文档大小成正比;upsert 兜住文档缺失。
  • 脚本更新解决依赖旧值的修改,参数化书写,密集脚本是 CPU 热点。
  • 删除即墓碑,空间靠段合并回收,磁盘滞后属正常。

单条文档的旅程走顺了,下一节放大流量:一千条文档同时进站,怎么搬才不翻车。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U