0

我正在使用带有 SQL 后端(MySQL 或 Postgres)的 Ruby On Rails(但这并不重要)。
Web 应用程序将是多进程的,有一组应用程序服务器进程在同一个数据库上运行和工作。

我想知道:有没有什么好的和通用的策略来处理比赛条件?

由于它将是一个 DB 密集型应用程序,我可以很容易地看到两个客户端如何尝试同时修改相同的数据。

让我们简化一下情况:

  • 两个客户/用户获取相同的数据,这是否同时发生并不重要。
  • 它们有两个代表相同数据的网页。
  • 后来他们俩都尝试对同一条记录写入一些不兼容的修改。

有没有一种简单的方法来处理这种情况?

我正在考虑使用与每条记录相关联的 id-tokens。此令牌将在记录更新时更改,从而使基于陈旧数据(旧的过期令牌)的任何后续更新尝试无效。

有没有更好的办法?也许已经在 MySQL 中内置了一些东西?我也对这种情况下使用的编码模式感兴趣。

谢谢

4

1 回答 1

5

乐观锁定

在 webapps 中处理这个问题的标准方法是使用所谓的“乐观锁定”。

每条记录都有一个唯一的 ID 和一个整数(或时间戳,但整数更好)乐观锁字段。此 oplock 文件在记录创建时初始化为 0。

当您获得记录时,您将获得 oplock 字段。

当您设置记录时,您将 oplock 值设置为您使用SELECT加一检索到的 oplock,并且您以UPDATEoplock 值仍然是您上次查看时的值为条件:

UPDATE thetable
SET field1 = ...,
    field2 = ...,
    oplock = 1
WHERE record_id = ...
  AND oplock = 0;

如果你在另一个会话中输掉了比赛,这个语句仍然会成功,但它会报告零行受影响。这使您可以告诉用户他们的更改与其他用户的更改发生冲突,或者合并他们的更改并重新发送,具体取决于应用程序的该部分中什么是有意义的。

许多框架都提供了工具来帮助实现这一点,并且大多数 ORM 可以开箱即用。Ruby on Rails 支持乐观锁定

在传统应用程序中将乐观锁定与悲观锁定(如下所述)结合使用时要小心。它可以工作,您只需要在所有可乐观锁定的表上添加一个触发器,该触发器会在UPDATEifUPDATE语句本身没有这样做时增加 oplock 列。我为 Hibernate oplock 支持编写了一个 PostgreSQL 触发器,它应该很容易适应 Rails。如果你要从 Rails 外部更新数据库,你只需要这个,但在我看来,确保安全总是一个好主意。

悲观锁定

更传统的方法是开始事务并SELECT ... FOR UPDATE在获取您打算修改的记录时执行。然后,当用户思考他们将要做什么并在tingUPDATE之前发布已经锁定的记录时,您将事务保持打开和空闲状态。COMMIT

这效果不好,我不推荐它。它要求每个用户都有一个开放的、通常是空闲的事务。这可能会导致 PostgreSQL 中的 MVCC 行清理出现问题,并可能导致应用程序中的锁定问题。对于具有高用户数的大型应用程序,它也非常低效。

插入比赛

处理比赛INSERT需要您在表上有一个合适的应用程序级别的唯一键,因此当它们发生冲突时插入会失败。

于 2013-04-16T01:45:58.910 回答