并发 - Spring + Hibernate + SQL 服务器
Concurrency - Spring + Hibernate + SQL Server
即使在 spring 事务中使用可序列化隔离级别后,我也遇到了并发问题。我的用例是用户将以以下格式提供要在数据库中更新的配置。
{A1: [B1, B2, B3]}
我必须将其保存在下面的实体中。
A {
@OneToMany
List<B> bList;
}
B {
@ManyToOne
A a;
Boolean isDeleted;
}
当有并发请求保存配置时,插入的 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].
即使经过大量研究,我也无法找到解决此问题的合适方法。
根据这篇文章 (https://sqlperformance.com/2014/04/t-sql-queries/the-serializable-isolation-level),使用 SQL 服务器时总是可能出现这种情况,因为操作顺序是有效的序列化之一。
最好通过为乐观锁定引入版本列来解决这个问题。无需使用 SERIALIZABLE 隔离级别。只需使用
A {
@Version
long version;
@OneToMany
List<B> bList;
}
并确保在加载 A
时使用 LockModeType.OPTIMISTIC_FORCE_INCREMENT
。这样,“序列化”将基于您所谓的“聚合根”的锁,即 A
.
这样一来,一个事务会成功,另一个会失败,因为在每个事务结束时,版本列只有在值没有同时发生变化的情况下才会递增。如果它同时发生变化,它将回滚两个事务之一,您将看到一个 OptimisticLockException。
即使在 spring 事务中使用可序列化隔离级别后,我也遇到了并发问题。我的用例是用户将以以下格式提供要在数据库中更新的配置。
{A1: [B1, B2, B3]}
我必须将其保存在下面的实体中。
A {
@OneToMany
List<B> bList;
}
B {
@ManyToOne
A a;
Boolean isDeleted;
}
当有并发请求保存配置时,插入的 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].
即使经过大量研究,我也无法找到解决此问题的合适方法。 根据这篇文章 (https://sqlperformance.com/2014/04/t-sql-queries/the-serializable-isolation-level),使用 SQL 服务器时总是可能出现这种情况,因为操作顺序是有效的序列化之一。
最好通过为乐观锁定引入版本列来解决这个问题。无需使用 SERIALIZABLE 隔离级别。只需使用
A {
@Version
long version;
@OneToMany
List<B> bList;
}
并确保在加载 A
时使用 LockModeType.OPTIMISTIC_FORCE_INCREMENT
。这样,“序列化”将基于您所谓的“聚合根”的锁,即 A
.
这样一来,一个事务会成功,另一个会失败,因为在每个事务结束时,版本列只有在值没有同时发生变化的情况下才会递增。如果它同时发生变化,它将回滚两个事务之一,您将看到一个 OptimisticLockException。