JPA with Isolation.READ_COMMITTED 充当 REPEATABLE_READ

问题描述 投票:0回答:1

我在我的测试 JPA 应用程序中看到一个有点奇怪的行为。

简单的表格;

id
count

@Transactional(隔离=隔离。READ_COMMITTED) 公共无效测试计数(){

 // I randomly fetch a row which has a count > 4
 Counts counts = myRepo.findFirstCountGreaterThanOrderByRandom(4);

 // This is a findById with explicit lock (SELECT * FROM counts WHERE id = 1 FOR UPDATE)
 counts = myRepo.findById(counts.getId());
 counts.setCount(1);
 log.info("counts = {}, counts);

}

这是一段简单的代码,它从数据库中随机获取计数大于 4 的行,然后使用 FOR UPDATE 使用 findById 再次调用数据库。

现在如果我这样做:

  • myRepo.findById(counts.getId());
  • 上放一个断点
  • 我打开一个 MySQL 终端并输入一个新的交易
     BEGIN
     UPDATE car SET count = 1; 
  • 在我下一步的代码中,它将纠正块,直到 MySQL 事务提交;
  • 在 MySQL 终端 I
    COMMIT;
  • 在代码中,锁被释放并继续到
    log.info

令我惊讶的是计数是 4...(而不是预期的 1)

如果我做相反的测试

  • 在代码中,我在
    log.info
    行的 FOR UPDATE 之后休息一下
  • 我打开一个 MySQL 终端
  • 列表项
    BEGIN
    SELECT * FROM counts where count > 4 LIMIT 1;
    SELECT * FROM counts where id = 1 FOR UPDATE // it will lock here, good
  • 在代码中我继续并让它提交;
  • 在 MySQL 终端中,选择继续并且计数正确 1

为什么在代码中,即使我指定了 READ-COMMITTED,行为也像可重复读取? 在 jpa 中,问题显然是第一次阅读,因为如果我删除它并在锁释放后直接选择更新,我会正确地得到正确的计数 1

我启用了所有事务调试日志,我可以看到正确的 READ-COMMITTED 已打开。

我正在使用 Spring Boot 2.7.0 和 MySQL 8.0

java spring-boot locking read-committed repeatable-read
1个回答
-1
投票

我简直不敢相信。 第二个选择是从 entityManager 缓存中读取值?这怎么可能。

entityManager.clear()
之前添加
myRepo.findById(counts.getId());
按预期产生正确的计数。

@vlad-mihalcea 欢迎您的专业知识。 这是正常的吗?为什么要从缓存中读取值。这没有任何意义,不是吗?

有多少应用程序不知道这一点并且目前容易受到此问题的影响?

© www.soinside.com 2019 - 2024. All rights reserved.