Spring Hibernate @Data更新与原始UPDATE查询:尽管有@Transactional,但乐观锁异常?

时间:2019-05-08 02:20:04

标签: mysql spring hibernate transactions spring-data-jpa

我正在使用Java Spring应用程序与Spring Hibernate ORM一起使用MYSQL DBMS进行数据管理。我有一个简单的要求-当有人使用我的应用程序时,我会将数据库中的计数器增加1,以便可以跟踪用户的使用次数。

在这里我要讨论两个简短的实现,一个实现给我一个在高负载下的乐观锁异常(我创建了许多并发用户的模拟来测试负载),而一个没有实现。有人可以指导我理解这些差异,这种现象背后的原因以及是否可以确保数据正确性吗?

这是我的拳头代码示例。在压力测试下,将抛出OptimisticLockException。

@Transactional
public void updateLog() {
    ConnectionUseLog log = getLog();
    log.setCount(log.getCount() + 1);
    // ... other unrelated updates to database
}

ConnectionUseLog是@Data类,具有@Version属性用于乐观锁定,并具有'count'属性(这是我要加1的值)。

@Data
public class ConnectionUseLog {
    @Version
    @Column(name="optlock_version")
    Integer version;
    @Column(name="count")
    Integer count;
}
  • getLog()是我编写的原始SELECT查询,获取所需的行。它使用springframework.data.repository中的PagingAndSortingRepository接口。
  • getCount(...)获取列中的值,方法自动生成。
  • setCount(...)更新计数,方法自动生成。

现在在下一个示例中,它对数据库的原始UPDATE查询(而不是像以前那样的SELECT)更加简单,明了和简单-没问题。还可以通过springframework.data.repository中的PagingAndSortingRepository接口嵌入原始查询,如上例所示。

@Transactional
public void updateDailyLog() {
    incrementCount(1);
    // ... other unrelated updates to database
}

当两者都包裹在@Transactional中时,为什么会有乐观的并发问题?

有人可以帮助您理解差异并帮助我权衡这两种方法吗?谢谢。

1 个答案:

答案 0 :(得分:0)

我不确定您为什么认为@Transactional应该使您的OptimisticLockingException消失。 乐观锁定就是关于不同事务的并发性。

它的工作原理是在每次交易中增加版本,并检查自加载实体以来该版本仍未更改的更新。

在高负载下,这通常对于一系列此类事件失败:

  1. 交易1(T1)使用版本23加载行说。
  2. 事务2(T2)加载具有相同版本23的同一行。
  3. T1更新计数,将值增加到24,并承诺使更改的值对其他事务可见。
  4. T2也会尝试更新该行,但是版本不再是23,因此您会获得乐观的锁定异常。
  5. 您应该重新启动T2,直到它成功更新数据库为止。

请注意,两个事务都不会在该行上放置阻塞锁。 在大多数情况下,这是一件好事,因为它可以提高性能。 如果第4件事经常发生,这种好处就消失了。

当您执行“原始更新”时,我猜像是UPDATE log set counter = counter + 1 WHERE ...一样,没有乐观的锁定。 相反,您使用的基本上是悲观锁定:读取时锁定要更新的行。 由于读和写发生在同一条语句中,因此隐式发生这种情况。 这意味着现在将发生如下类似的过程:

  1. 事务1(T1)更新了对其进行锁定的行。
  2. 事务2(T2)尝试更新同一行,但被锁阻塞,必须等待。
  3. T1提交其事务,使其他事务可以看到更改并释放锁。
  4. T2现在可以继续进行操作,并根据T1中更改后的值更新行。

该锁限制了应用程序的吞吐量,尤其是在事务较长且行被锁定的情况下,实际上这些行最终不会更新。 在您的示例中不是这种情况,但是在更典型的示例中,用户加载一行以查看并可能对其进行编辑更为常见。

在您的情况下,采用直接更新的方法似乎更合适。 只要确保交易越短越好。