我正在使用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;
}
现在在下一个示例中,它对数据库的原始UPDATE查询(而不是像以前那样的SELECT)更加简单,明了和简单-没问题。还可以通过springframework.data.repository中的PagingAndSortingRepository接口嵌入原始查询,如上例所示。
@Transactional
public void updateDailyLog() {
incrementCount(1);
// ... other unrelated updates to database
}
当两者都包裹在@Transactional中时,为什么会有乐观的并发问题?
有人可以帮助您理解差异并帮助我权衡这两种方法吗?谢谢。
答案 0 :(得分:0)
我不确定您为什么认为@Transactional
应该使您的OptimisticLockingException
消失。
乐观锁定就是关于不同事务的并发性。
它的工作原理是在每次交易中增加版本,并检查自加载实体以来该版本仍未更改的更新。
在高负载下,这通常对于一系列此类事件失败:
请注意,两个事务都不会在该行上放置阻塞锁。 在大多数情况下,这是一件好事,因为它可以提高性能。 如果第4件事经常发生,这种好处就消失了。
当您执行“原始更新”时,我猜像是UPDATE log set counter = counter + 1 WHERE ...
一样,没有乐观的锁定。
相反,您使用的基本上是悲观锁定:读取时锁定要更新的行。
由于读和写发生在同一条语句中,因此隐式发生这种情况。
这意味着现在将发生如下类似的过程:
该锁限制了应用程序的吞吐量,尤其是在事务较长且行被锁定的情况下,实际上这些行最终不会更新。 在您的示例中不是这种情况,但是在更典型的示例中,用户加载一行以查看并可能对其进行编辑更为常见。
在您的情况下,采用直接更新的方法似乎更合适。 只要确保交易越短越好。