2017-06-16 87 views
2

我正在使用Spring Boot在Java中实现一个REST API。我使用了嵌入式内存数据库H2几个星期,但在某些时候我发现事务隔离有问题。Spring JPA + MySQL和死锁

更确切地说,我有一张表,我需要跟踪“重复”记录。重复只是一个记录,这个记录对于表格列的定义良好的子集是相同的。所以,基本上,当我插入新记录时,我首先检查它是否是重复的,并相应地标记它。一个布尔列“重复”用于此目的。

例如,假设B和C是我检查的列以定义重复项。这是一个有效的状态:

| A | B | C | duplicate | | - | - | - | --------- | | x | y | z | false | | z | y | z | true | | x | y | y | false | | x | y | y | true | | y | y | y | true |

,而这是不一个有效状态:

| A | B | C | duplicate | | - | - | - | --------- | | x | y | z | false | | z | y | z | true | | x | y | y | false | | x | y | y | true | | y | y | y | false |

...因为行3和行5对两个B相同的值和C,因此必须将其中的一个标记为重复。

换句话说,我的要求是将任何恰好已经使用过的值标记为重复。对于给定的一组值,只有一行将被允许具有duplicate == 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属性(字符串类型)和duplicate属性(类型布尔值)。重复检查基于bar属性。

随着H2我有很多错过重复(通常10%)。使用MySQL我总是有正确的结果(即标记为重复的行数为,正好为 N - 1,N为插入行数)。唯一的问题是只有一小部分插入成功(最多从1%到30%)。

我得到了大量死锁相关的异常。这是为什么?这样简单的代码怎么会导致死锁。我的意思是,这只是一个选择,然后是插入。

有什么建议吗?

+0

您使用的H2和商店引擎的版本是什么? – fg78nc

+0

我不知道如何检查它。我发现了一些MySQL代码来查询给定表的引擎,但它似乎没有在H2上工作。至于版本,我在我的'pom.xml'中列出了依赖项,没有版本号,所以也许它是最新的(?)。 –

+1

请尝试添加到数据库URL'LOCK_MODE = 1; MVCC = TRUE;' – fg78nc

回答

1

我认为死锁相关的异常是由我测试演示程序的方式引起的。更确切地说,测试代码是用JavaScript/Node.js编写的,当启动I/O任务时,它的速度非常快,为。所有交易几乎同时要求(也许自动同时重试?)。

通过在每个请求之间添加一个非常短的等待时间(例如10毫秒),我获得了合理的吞吐量和非常低数量的死锁相关异常。

我的猜测是根本没有死锁。只是非常高的锁争用,在数据库级别的某种启发式解释为可能的死锁。实际上,通过禁用MySQL CLI中的死锁检测,我完全消除了那些与死锁相关的异常(尽管它们被锁等待超时取代)。

2

应用程序不应检查事务中的重复密钥本身。将其留给具有唯一索引的数据库引擎,如果发生异常则捕获该异常,并用另一个标识符再次尝试。

如果您真的想在应用程序级别解决此问题,也许您应该在打开事务后手动锁定表。隔离级别可以自动为您执行此操作,但性能成本很高(您可能不想要)。

另一种解决方案是乐观锁定,使用@Version注释,但是您将无法保证标识符的唯一性。


这是很难诊断您的僵局问题,但是当你有递归交易(交易在另一个事务中打开)它通常出现。检查你的豆@Scope,他们可以创建这样的问题。此外,请确保您只有一个TransactionManager和一个EntityManager bean。

+1

嗨Guillaume,谢谢。在数据库级别检查重复项是一个合理的选项,但我更愿意将检查保留在应用程序级别。至于死锁,我的演示应用程序非常简单,当然,我的bean没有嵌套事务或非默认(单例)范围。我的猜测是,所有那些与死锁相关的异常都只是一些死锁检测启发式的结果,但没有实际的死锁。 –