先记住这个答案
PUT 是全量替换语义,请求体应包含资源的完整表示,服务器用该表示整体替换目标资源,且重复请求结果一致,因此是幂等的。PATCH 用于部分更新,请求体是描述如何修改资源的指令,例如 JSON Patch,它只更新指定字段,但变更指令本身可能包含自增或追加操作,重复执行可能产生不同结果,因此不保证幂等。例如将计数器加 1 的 PATCH 指令重复发送会导致值不断增加,而 PUT 直接覆盖为固定值则结果恒定。
- PUT 整体替换资源,语义幂等。
- PATCH 部分修改,语义不保证幂等。
- PATCH 幂等性取决于指令类型,设计时需谨慎。
语义机制与幂等性推导
PUT 的 HTTP 语义并未规定必须使用 JSON,但它的本质是“用请求里的表示替换目标资源的当前表示”。因此规范上服务端收到 PUT 后,应将资源的全部字段重置为请求体中的值,未提供的字段要么被清空(若允许)要么视为缺失。这种替换操作天然幂等:不论执行一次还是十次,资源最终都变成同一个固定表示。
PATCH 的 RFC 5789 将其定义为“对资源应用部分修改”,请求体起到的是操作指令作用。指令可以形如 {"op":"replace","path":"/status","value":"suspended"},也可以是直接发给服务端的一个对象子集,取决于媒体类型。而“自增”这类指令会让第二次执行结果不同于第一次,因此不满足幂等。即便多数 PATCH 实现只是合并字段,只要指令不包含相对变化,重复请求也常能保持幂等,但这属于实现行为而非规范保证。
用户资料编辑接口的选型
假设有一个用户资源,结构为 {id, name, email, device: {platform, version}}。现在客户端要允许用户只更改邮箱地址,服务端收到表单后只应更新 email 字段。若用 PUT,客户端必须回传包括 name 和 device 在内的全部字段,否则服务端无法区分“未提供”与“清空”,可能覆盖掉用户之前的姓名或设备信息。
因此这里应选择 PATCH:请求体只发送 {"email":"new@example.com"} 或 JSON Patch 指令,服务端仅检查并更新该字段,其他状态不动。这就是“部分更新”的落地场景。反过来,如果编辑页面展示的是完整表单并允许修改所有字段,提交时携带完整数据且服务端整体覆盖,那么 PUT 更合适,因为服务端能拿到完整状态,不必担心字段被意外清空。
适用边界与条件请求
PATCH 的灵活性带来一个问题:它不保证幂等,也就无法像 PUT 那样依靠重试来恢复一致。比如一个将 quantity 减 1 的 PATCH 指令,在网络超时后客户端若直接重发,商品库存会被扣两次。因此任何变更指令若包含“相对量”或“自增”操作,就必须禁止重试或要求客户端使用唯一操作 ID 去重,而 PUT 只需重发完整新状态即可安全收敛。
并发控制时,真正可靠的不是方法本身而是条件头。要对更新时间敏感的场景,可在 PUT 或 PATCH 中携带 If-Match 头并使用服务端返回的 ETag,当资源已被他人修改时服务端返回 412。否则即便用 PUT 实现全量替换,也无法避免后写覆盖先写的丢失更新问题。
PATCH 请求的响应若返回 200,通常会带上更新后的完整资源表示;若返回 204,则表示修改已生效但响应没有正文。客户端需要根据响应头如 ETag 同步本地缓存,而不是假定 PATCH 一定返回新的资源内容。
容易答错的地方
- 认为 PATCH 一定比 PUT 更轻量
- 常有人以为 PATCH 请求体必然更小,从而不计后果地使用。但实际上 PATCH 的指令集可能比 PUT 的全量 JSON 更大更繁琐。例如 JSON Patch 的常用指令结构包含
op/path等字段,部分指令还需value,不仅冗余还增加解析复杂度。是否使用 PATCH 应依据“客户端是否掌握完整新状态”决定,与请求体大小无关。 - 混淆“幂等”与“重复请求结果相同”
- 重复执行同一 PUT 请求,因为替换为相同表示,结果必然一致,故 PUT 是幂等的。而 PATCH 如果只是替换一个字段,重复发两次同样得到该字段的同一固定值,结果也一致,但这不说明 PATCH 被规范定义为幂等。RFC 5789 明确 PATCH 不保证幂等,因为指令可能包含相对操作。正确的理解是:PATCH 的幂等性取决于指令类型,不能一概而论。
面试官还会怎么问?
服务端如何区分 PATCH 请求体是“字段子集”还是“操作指令”?
取决于 Content-Type。若使用 application/json,通常约定为部分字段合并;若使用 application/json-patch+json,则必须遵循 JSON Patch 规范,每个操作必须包含 op 和 path,部分操作(如 add、replace)还需提供 value。服务端根据媒体类型解析,不可混用。可在 OPTIONS 响应的 Accept-Patch 字段中声明支持的 PATCH 格式。
若 PATCH 请求不幂等,在重试机制中如何处理?
设计应避免不幂等的 PATCH 指令。如果确实需要自增或追加,利用 If-Match 加 ETag 强制乐观锁,重试时必须先 GET 最新 ETag 再提交,否则失败。另一种做法是请求携带唯一幂等键,服务端记录已处理过的请求 ID,重复提交直接返回之前结果。
部分更新时用 PUT 搭配 null 值操作有什么风险?
风险在于客户端无法知道哪些字段可被空值覆盖,语义不明确。服务端若把 null 视为“清空”,而客户端只是未加载该字段,就会误删数据。PATCH 明确只改动发送的字段,天然规避此问题,因此更安全。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。