9

当使用乐观锁定策略时,它可以解决如下并发问题:

| 第一笔交易开始|
| |  
| 选择一行 |
| | 第二笔交易开始
| 使用版本检查更新行 |
| | 选择同一行
| 提交交易 |
| | 使用版本检查更新行
| |  
| | 因为版本脏了所以回滚

但是,如果在极少数情况下,如果第二个事务中的更新是在第一个事务中的 udpate 之后但在事务提交之前呢?

| 第一笔交易开始|
| | 第二笔交易开始
| 选择一行 |
| | 选择同一行
| 使用版本检查更新行 |
| | 使用版本检查更新行
| 提交交易 |
| | 因为版本脏了所以回滚 // 会吗?
| |  
| |  

我做了一个实验,第二个事务中的更新无法读取“脏”版本,因为第一个事务尚未提交。在这种情况下,第二次交易会失败吗?

4

2 回答 2

3

你没有在你的问题中说你实际使用的是什么数据库系统,所以我不知道你系统的细节。

但无论如何,在乐观锁定系统下,进程在执行更新语句时不能只检查行版本,因为正是您担心的问题。

对于完全可序列化的隔离事务,每个进程必须在提交时自动检查它检查和修改的所有行的行版本。因此,在您的第二种情况下,右手进程在尝试提交之前不会检测到冲突(右手进程未包括的步骤)。当它尝试提交时,它将检测到冲突并回滚。

于 2013-06-03T06:28:07.737 回答
1

正如您已经发现的那样,乐观锁定受TOCTOU竞争条件的影响:在提交决定和实际提交之前,有一个很短的时间窗口,在此期间另一个事务可以修改数据。

为了使乐观锁定 100% 安全,您必须确保第二个事务等到第一个事务提交,然后才进行版本检查:

在此处输入图像描述

您可以通过在更新语句之前获取行级(选择更新)锁来实现此目的。

jOOQ为您做到这一点。在 Hibernate 中,您必须手动锁定行:

var pessimisticRead = new LockOptions(LockMode.PESSIMISTIC_READ);
session.buildLockRequest(pessimisticRead).lock(entity);

请注意,您无法在单个 VM 上在 Hibernate 中重现令人讨厌的 TOCTOU 竞争条件。由于共享的持久上下文,Hibernate 将顺利解决这个问题。当事务在不同的虚拟机上运行时,Hibernate 无能为力,您必须添加额外的锁定。

于 2021-01-09T14:20:22.230 回答