使用乐观锁定策略时,它可以解决如下的并发问题:
| the first transaction started | | | | select a row | | | the second transaction started | update the row with version checking | | | select the same row | commit txn | | | update the row with version checking | | | | rolls back because version is dirty
但是,如果在极少数情况下,第二个事务中的更新是在第一个事务中的udpate之后但是在事务提交之前呢?
| the first transaction started | | | the second transaction started | select a row | | | select the same row | update the row with version checking | | | update the row with version checking | commit txn | | | rolls back because version is dirty // will it? | | | |
我做了一个实验,第二个事务中的更新无法读取'脏'版本,因为第一个事务尚未提交。在这种情况下,第二笔交易会失败吗?
答案 0 :(得分:2)
您没有在问题中说明您实际使用的数据库系统,因此我不知道您系统的详细信息。
但无论如何,在乐观锁定系统下,进程不能只在执行更新语句时检查行版本,因为你担心的问题确实存在。
对于完全可序列化的隔离事务,每个进程必须在提交时以原子方式检查它检查和修改的所有行的行版本。因此,在您的第二个场景中,右手进程在尝试提交之前不会检测到冲突(您没有为右侧进程包含的步骤)。当它尝试提交时,它将检测冲突并回滚。
答案 1 :(得分:0)
正如您已经发现的那样,乐观锁定受制于 TOCTOU 竞争条件:在提交决定和实际提交之前,有一个很短的时间窗口,在此期间另一个事务可以修改数据。
为了使乐观锁 100% 安全,您必须确保第二个事务等到第一个事务提交,然后才进行版本检查:
您可以通过在更新语句之前获取行级(选择更新)锁来实现这一点。
jOOQ does 适合您。在 Hibernate 中,您必须手动锁定该行:
var pessimisticRead = new LockOptions(LockMode.PESSIMISTIC_READ);
session.buildLockRequest(pessimisticRead).lock(entity);
请注意,您无法在单个 VM 上的 Hibernate 中重现烦人的 TOCTOU 竞争条件。由于共享持久上下文,Hibernate 将顺利解决这个问题。当事务在不同的 VM 上运行时,Hibernate 无法提供帮助,您必须添加额外的锁定。