我正在使用 Ruby On Rails(但这并不重要)和 SQL 后端,MySQL 或 Postgres。
Web 应用程序将是多进程的,有一组应用程序服务器进程在同一个数据库上运行和工作。
我想知道:是否有任何好的通用策略来处理比赛条件?
由于这将是一个数据库密集型应用程序,我可以很容易地看到两个客户端如何同时尝试修改相同的数据。
让我们简化一下情况:
- 两个客户/用户获取相同的数据,这是否同时发生并不重要。
- 它们使用表示相同数据的两个网页提供服务。
- 后来他们都尝试对同一条记录进行一些不兼容的修改。
有没有一种简单的方法来处理这种情况?
我正在考虑使用与每条记录关联的 id-tokens。此 token 将在记录更新时更改,从而使基于陈旧数据(旧的过期 token )的任何后续更新尝试无效。
有没有更好的方法?也许已经在 MySQL 中构建了一些东西? 我也对这种情况下使用的编码模式感兴趣。
谢谢
最佳答案
乐观锁
在 webapps 中处理这个问题的标准方法是使用所谓的“乐观锁定”。
每条记录都有一个唯一的ID和一个整数(或时间戳,但整数更好)乐观锁字段。此 oplock 字段在记录创建时初始化为 0。
当您获得记录时,您将获得 oplock 字段。
当您设置记录时,您将 oplock 值设置为您使用 SELECT
加一检索到的 oplock,并使 UPDATE
以 oplock 值仍然是什么为条件这是你最后一次看的时候:
UPDATE thetable
SET field1 = ...,
field2 = ...,
oplock = 1
WHERE record_id = ...
AND oplock = 0;
如果你输掉了与另一个 session 的比赛,这个语句仍然会成功,但它会报告零行受影响。这允许您告诉用户他们的更改与另一个用户的更改发生冲突,或者合并他们的更改并重新发送,具体取决于应用的该部分的意义。
许多框架都提供工具来帮助自动执行此操作,而且大多数 ORM 都可以开箱即用。 Ruby on Rails supports optimistic locking .
为传统应用程序组合乐观锁定和悲观锁定(如下所述)时要小心。它可以工作,您只需要在所有可乐观锁定的表上添加一个触发器,如果 UPDATE
语句本身没有这样做,它会增加 UPDATE
上的 oplock 列。我 wrote a PostgreSQL trigger for Hibernate oplock support应该很容易适应 Rails。仅当您要从 Rails 外部更新数据库时才需要它,但在我看来,确保安全始终是个好主意。
悲观锁
更传统的方法是开始事务并在获取要修改的记录时执行 SELECT ... FOR UPDATE
。然后,当用户思考他们将要做什么并在 COMMIT
之前对已锁定的记录发出 UPDATE
时,您将事务保持打开和空闲状态。
这不是很好,我不推荐它。它要求每个用户都有一个开放的、通常是空闲的交易。这可能会导致 PostgreSQL 中的 MVCC 行清理出现问题,并可能导致应用程序中出现锁定问题。对于用户数量多的大型应用程序,它的效率也非常低。
插入比赛
处理 INSERT
上的竞争需要您在表上有一个合适的应用程序级唯一键,因此当它们发生冲突时插入会失败。
关于mysql - 关系数据库竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16027155/