并发 - Spring + Hibernate + SQL Server

rup*_*esh 5 sql-server concurrency hibernate spring-data

即使在spring事务中使用了可序列化隔离级别后,我也遇到了并发问题。我的用例是用户将以以下格式提供要在数据库中更新的配置。

{A1: [B1, B2, B3]}
Run Code Online (Sandbox Code Playgroud)

我必须将其保存在下面的实体中。

A {
    @OneToMany
    List<B> bList;
}

B {
    @ManyToOne
    A a;
    
    Boolean isDeleted;
}
Run Code Online (Sandbox Code Playgroud)

当有并发请求保存配置时,插入的 B 比预期的多。请参考以下场景。

Initial enitites in database: A1 -> []

Transaction 1 - given config {A1: [B2]}

Reads A1 -> []
Insert B2

Transaction 2 - given config {A1: [B3]}

Reads A1 -> []
Insert B3

Final in database: A1 -> [B2, B3] when expected is either A1 -> [B2, B3-deleted] or A1 -> [B2-deleted, B3].
Run Code Online (Sandbox Code Playgroud)

即使经过大量研究,我也无法找到解决此问题的适当方法。根据这篇文章(https://sqlperformance.com/2014/04/t-sql-queries/the-serializable-isolation-level),这种情况在使用 SQL Server 时总是可能的,因为操作顺序是其中之一有效的序列化。

Chr*_*kov 1

最好的解决方法是引入乐观锁定的版本列。无需使用 SERIALIZABLE 隔离级别。只需使用

A {
    @Version
    long version;
    @OneToMany
    List<B> bList;
}
Run Code Online (Sandbox Code Playgroud)

并确保LockModeType.OPTIMISTIC_FORCE_INCREMENT加载时使用A。这样,“序列化”将基于所谓的“聚合根”的锁,即A.

通过这样做,一个事务将成功,而另一个事务将失败,因为在每个事务结束时,仅当值在此期间没有更改时版本列才会递增。如果它同时发生变化,它将回滚两个事务之一,您将看到 OptimisticLockException。