怎么避免两个请求同时改同一行而互相覆盖?乐观锁的 UPDATE 长什么样?冲突了怎么办?这篇在真实的 PostgreSQL 上用多线程把无锁、乐观锁、乐观锁加重试的结果摆在一起。
期望 n = 20,实际库里 n = 10 ORM 发出的 UPDATE: UPDATE plain SET n=%(n)s::INTEGER WHERE plain.id = %(plain_id)s::INTEGER
上一篇:expire_on_commit;下一篇:悲观锁。
两个会话读到的版本号: 1 1 先提交的会话发出的 UPDATE: UPDATE versioned SET n=%(n)s::INTEGER, ver=%(ver)s::INTEGER WHERE versioned.id = %(versioned_id)s::INTEGER AND versioned.ver = %(versioned_ver)s::INTEGER 后提交的会话 → StaleDataError: UPDATE statement on table 'versioned' expected to update 1 row(s); 0 were matched. 库里 n = 10 | ver = 2
没有锁(Plain) 期望 160,最终 n = 25 | 冲突/重试 0 次 | 76 ms 乐观锁,冲突就放弃(Versioned) 期望 160,最终 n = 25 | 冲突/重试 135 次 | 85 ms 乐观锁 + 重试(Versioned) 期望 160,最终 n = 160 | 冲突/重试 727 次 | 502 ms
线程里每个都用自己的 Session,sleep(1 ms) 放大读改写之间的窗口。
GET → ETag: "1"
第一个客户端带 If-Match 更新 → 200
第二个客户端用同一个旧 ETag 更新 → 412 {'error': '版本已变化'}
重新 GET 得到的新 ETag: "2"