乐观锁定
在 webapps 中处理这个问题的标准方法是使用所谓的“乐观锁定”。
每条记录都有一个唯一的 ID 和一个整数(或时间戳,但整数更好)乐观锁字段。此 oplock 文件在记录创建时初始化为 0。
当您获得记录时,您将获得 oplock 字段。
当您设置记录时,您将 oplock 值设置为您使用SELECT
加一检索到的 oplock,并且您以UPDATE
oplock 值仍然是您上次查看时的值为条件:
UPDATE thetable
SET field1 = ...,
field2 = ...,
oplock = 1
WHERE record_id = ...
AND oplock = 0;
如果你在另一个会话中输掉了比赛,这个语句仍然会成功,但它会报告零行受影响。这使您可以告诉用户他们的更改与其他用户的更改发生冲突,或者合并他们的更改并重新发送,具体取决于应用程序的该部分中什么是有意义的。
许多框架都提供了工具来帮助实现这一点,并且大多数 ORM 可以开箱即用。Ruby on Rails 支持乐观锁定。
在传统应用程序中将乐观锁定与悲观锁定(如下所述)结合使用时要小心。它可以工作,您只需要在所有可乐观锁定的表上添加一个触发器,该触发器会在UPDATE
ifUPDATE
语句本身没有这样做时增加 oplock 列。我为 Hibernate oplock 支持编写了一个 PostgreSQL 触发器,它应该很容易适应 Rails。如果你要从 Rails 外部更新数据库,你只需要这个,但在我看来,确保安全总是一个好主意。
悲观锁定
更传统的方法是开始事务并SELECT ... FOR UPDATE
在获取您打算修改的记录时执行。然后,当用户思考他们将要做什么并在tingUPDATE
之前发布已经锁定的记录时,您将事务保持打开和空闲状态。COMMIT
这效果不好,我不推荐它。它要求每个用户都有一个开放的、通常是空闲的事务。这可能会导致 PostgreSQL 中的 MVCC 行清理出现问题,并可能导致应用程序中的锁定问题。对于具有高用户数的大型应用程序,它也非常低效。
插入比赛
处理比赛INSERT
需要您在表上有一个合适的应用程序级别的唯一键,因此当它们发生冲突时插入会失败。