← 中文资源

简体中文版本

用版本列添加乐观锁

通过版本列和版本谓词更新防止丢失更新,并在两个写者并发修改同一行时返回 409。

阅读英文原文

为什么最后写入会赢

盲目的 UPDATE ... WHERE id = $1 会覆盖当前存储的任何内容。Alice 加载版本 3,Bob 也加载版本 3,两人都保存时,Bob 的保存会让 Alice 的修改消失。数据库只是执行了要求;应用没有表达“该行自读取后未被修改”的期望。

版本谓词把这个期望变成条件。只有存储版本等于客户端读取版本时才更新。版本递增在同一语句中原子完成,因此两个并发更新不可能同时满足谓词。

UPDATE documents
   SET content = $1, version = version + 1
 WHERE id = $2 AND version = $3
RETURNING version;

返回冲突,而不是静默覆盖

rowCount 为零时,行已删除或版本已改变。若可安全暴露,返回 409 和当前版本,让客户端重新加载并合并。不要自动用新版本重试更新,那会覆盖另一个写者的修改,违背初衷。

测试并发

顺序调用两次更新的单元测试不能证明谓词有效。用相同起始版本启动两个事务或并行请求,断言恰好一个成功。PostgreSQL 也可以用 SELECT ... FOR UPDATE 做悲观锁,但它会在用户编辑期间持锁,不适合长会话。

验证冲突

预期结果:两个并发更新中一个成功并递增版本,另一个返回 409 且不修改内容。用并行请求测试确认,并检查 409 响应携带当前版本供客户端合并。