java-Spring JPA MySQL和死锁
作者:互联网
我正在研究使用Spring Boot在Java中实现的REST API.我使用了嵌入式内存数据库H2数周,但在某个时候我注意到事务隔离存在问题.
更准确地说,我有一个表,需要在其中跟踪“重复”记录.重复只是一条记录,对于表的列的定义明确的子集而言,它等于另一条记录.因此,基本上,当我插入新记录时,我首先检查它是否重复并相应地标记它.布尔列“ duplicate”用于此目的.
例如,假设B和C是我为了定义重复项检查的列.这是有效状态:
| A | B | C |重复|
| -| -| -| ——— |
| x | y | z |错误
| z | y | z |真实|
| x | y | y |错误
| x | y | y |真实|
| y | y | y |真实|
虽然这不是有效状态:
| A | B | C |重复|
| -| -| -| ——— |
| x | y | z |错误
| z | y | z |真实|
| x | y | y |错误
| x | y | y |真实|
| y | y | y |错误
…因为第3行和第5行的B和C值相同,因此必须将两者之一标记为重复.
换句话说,我的要求是将碰巧已经使用过的任何行标记为重复.给定一组值的仅一行将允许重复== false.
但是,基于Spring的实现无法按预期工作.例如,插入100个具有相同值的行应导致99个重复项,而只有一个非重复项.但是,当我尝试并行执行这些插入操作时,没有检测到很多重复项.
我尝试了几个修复程序,并且在某个时候我开始认为H2没有正确实现SERIALIZABLE隔离级别.我创建了一个小应用程序来演示它:
@RestController
public class NewFooCtrl {
@Autowired
private FooRepo repo;
@RequestMapping(value = "/foo", method = RequestMethod.POST)
@Transactional(isolation = Isolation.SERIALIZABLE)
public void newFoo(@RequestBody Foo foo) {
List<Foo> foos = repo.findByBar(foo.getBar());
if (foos.isEmpty()) foo.setDuplicate(false);
else foo.setDuplicate(true);
repo.save(foo);
}
}
注意:我省略了明显的代码,例如模型和存储库. Foo模型具有一个标识符(UUID类型),一个bar属性(String类型)和一个重复属性(boolean类型).重复检查基于bar属性.
使用H2时,我会错过很多重复项(通常为10%).使用MySQL我总是有正确的结果(即标记为重复的行数正好是N-1,其中N是插入的行数).唯一的问题是只有一小部分插入成功(最多从1%到30%).
我遇到了大量与死锁相关的异常.这是为什么?这样简单的代码怎么会导致死锁.我的意思是,这只是选择,然后是插入.
有什么建议吗?
解决方法:
应用程序不应在事务中检查重复的密钥本身.使用唯一索引将其留给数据库引擎,如果发生异常,则捕获异常,然后使用另一个标识符重试.
如果您确实想在应用程序级别解决此问题,则也许应该在打开事务后立即手动锁定表.隔离级别可以自动为您执行此操作,但是会带来较高的性能成本(您可能不希望这样做).
另一个解决方案是使用@Version批注进行乐观锁定,但是那样您将不能保证标识符的唯一性.
很难诊断死锁问题,但是通常在您具有递归事务(在另一个事务中打开一个事务)时出现.检查您的bean @Scope,它们可以创建此类问题.另外,请确保只有一个TransactionManager和一个EntityManager bean.
标签:jpa,deadlock,spring,java,mysql 来源: https://codeday.me/bug/20191111/2018614.html