使用 ETag 防止更新被覆盖:设计原理
一句话总结
只有把“仅当是我读到的那个版本时才修改”这个条件一直传递到数据库的写入语句,才能防止更新丢失。
为什么需要它
A 和 B 读到了同一条备忘 v1。A 修改标题之后,B 在过期的页面上保存,无条件的 UPDATE 就会悄无声息地覆盖 A 的修改。即使在保存之前用 SELECT 确认了版本,在确认和 UPDATE 之间,也可能有别的请求插进来。必须把比较和更新合并成一条 SQL 语句,并检查被修改的行数。这里会真实地打开 SQLite 文件,让通过独立连接发出的两个请求相互竞争。不能只检查进程内存里的字典。
工作原理
GET → ETag: "v1"
├─ A: PUT If-Match "v1" → UPDATE WHERE version=1 → 204, version=2
└─ B: PUT If-Match "v1" → 변경 행 0 → 412
ETag 是表示的验证器。本实验在这样的限制下生成强标签:每个 id 的 title 变化时,version 恰好递增,而响应表示的其他要素不会改变。在真实服务中,如果表示的压缩、语言、按权限显示的字段各不相同,就不能无条件地重用同一个版本字符串。
在现场相遇的样子
If-Match 使用强比较。这个教学用的 API 只接受单个标签,不支持 weak 标签、通配符和列表。这并不是实现了完整的 HTTP 语法,而是 API 中明确规定的一个狭窄的契约。不存在的备忘定为 404,缺少条件定为 428,不支持的条件或过期的版本定为 412。客户端不应无限重试 412,而应读取最新的备忘,请用户进行合并。属于输入错误的空标题,则用 422 来区分。
下一项实验要做什么
按照模式 → 创建 → 查询 → 标签 → 条件解析 → 原子更新 → 结果码 → FastAPI 的顺序完成。同时确认原来的标题是否得到保留。只有数字 412 是对的、却在后面执行了 UPDATE 的实现,不应该通过。DB 连接在每次操作后都要关闭,以防测试之间泄漏状态。
参考:HTTP 条件请求