我在我的测试 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());
BEGIN
UPDATE car SET count = 1;
COMMIT;
log.info
令我惊讶的是计数是 4...(而不是预期的 1)
如果我做相反的测试
log.info
行的 FOR UPDATE 之后休息一下 BEGIN
SELECT * FROM counts where count > 4 LIMIT 1;
SELECT * FROM counts where id = 1 FOR UPDATE // it will lock here, good
为什么在代码中,即使我指定了 READ-COMMITTED,行为也像可重复读取? 在 jpa 中,问题显然是第一次阅读,因为如果我删除它并在锁释放后直接选择更新,我会正确地得到正确的计数 1
我启用了所有事务调试日志,我可以看到正确的 READ-COMMITTED 已打开。
我正在使用 Spring Boot 2.7.0 和 MySQL 8.0
我简直不敢相信。 第二个选择是从 entityManager 缓存中读取值?这怎么可能。
在
entityManager.clear()
之前添加 myRepo.findById(counts.getId());
按预期产生正确的计数。
@vlad-mihalcea 欢迎您的专业知识。 这是正常的吗?为什么要从缓存中读取值。这没有任何意义,不是吗?
有多少应用程序不知道这一点并且目前容易受到此问题的影响?