为什么最后写入会赢
盲目的 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 响应携带当前版本供客户端合并。