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