繁体   English   中英

使用select max()然后插入max()+ 1来执行Hibernate并发问题

[英]Hibernate concurrency issue with select max() and then insert max() + 1

我有一个表有一些列和一个序列,该列为这些列的值递增。 对于(简化)示例:

A | B | SEQ
===========
x | x | 1
x | x | 2
x | x | 3
x | y | 1
x | x | 4
z | x | 1
x | y | 2

我有多个进程将使用带有Hibernate的Spring应用程序并接受A和B,选择A和B的最大值,然后插入max + 1

所以,对于上面的例子,如果有人提供A = z和B = x,我们选择,得到1并插入z,x,2。

如果某人提供了A = z且B = z(没有现有行匹配),则该过程将查询并找不到最大值(或最大值为零),然后插入z,z,1。

假设一个简单的序列在这里不起作用,因为序列的列大小不足以允许一个序列跨越A和B的所有值,并且因为实际问题比上面提供的示例更复杂。

我可以想到这个问题的四个解决方案可能有用,但我想知道是否有更标准的方法来实现这一点,我不确定如何使用Hibernate实现这些方法,并且不使用自定义Oracle功能:

  1. 执行select max() ,尝试插入。 如果存在并发问题,其中一个线程将失败,并且代码将被编写为根据需要重试多次,直到插入工作。 使用Hibernate,这可能意味着我们必须在事务中间手动刷新。
  2. 当我们执行初始select max()时,执行select for update或其他一些机制来锁定我们关心的行(匹配A和B的行select max() 这将阻止其他正在尝试查找最大值的进程,然后我们可以插入行,当txn提交时,将允许等待的选择进行选择。 我不确定如果表中当前没有与A和B匹配的行,这将如何工作。
  3. 创建一个执行max select并以原子方式插入的存储过程。 不过,如果可能的话,我想避免存储过程。
  4. 在Hibernate中,有没有办法进行插入( select max() + 1 )并从插入一个语句的列中返回值?

不确定这是否适合你。 但这就是我要做的,因为试图保持并发可能很复杂。

首先,我节省插入时间。

INSERT INTO yourTable (`A`, `B`, `insertTime`) VALUES(x, x, NOW() );

然后你的查询成为

SELECT `A`, `B`,
       ROW_NUMER() OVER (PARTITION BY `A`, `B` ORDER BY `insertTime`) as SEQ
FROM yourTable
//optional to get the exact result you show
ORDER BY `insertTime`

编辑:如果您想删除并在最后一个seq后继续增长,这可能不起作用。

那么聚合表怎么样:

A | B | LAST_SEQ
===========
x | x | 4
z | x | 1
x | y | 2

然后,您可以轻松使用行级锁定在第一个表中安全地生成记录。

您希望执行此read-increment-update / insert的事务可序列化(如“SET TRANSACTION ISOLATION LEVEL SERIALIZABLE”),该隔离级别使DB尝试查找顺序运行事务的顺序并获得相同的顺序结果; 如果它不能,它不允许其中一些通过。 我在Postgres CLI中尝试了你的场景,它不允许插入(提交错误)或冲突更新(第二次更新完成错误,事务中止); 我认为Oracle的行为类似,但细节可能会有所不同。 即使select的标准在不同的事务中是不同的,这也是有效的:例如,第一个事务由A选择,第二个由B选择,两者都进行更改,这些更改无论如何都会影响任何先前的选择 - 您会收到错误。 然后,我猜,重试。 注意与SERIALIZABLE隔离相关的可能死锁,这会导致错误(但是你已经有了重试,所以没关系)。

顺便说一下,对于这种更新的正确性,REPEATABLE READ隔离级别就足够了,但不适用于插入。

至于如何在Hibernate中使用不同的事务隔离级别(您可能不希望所有事务都可序列化,因为它会影响性能),我只能考虑从两个配置文件配置两个会话工厂(或者在代码中手动配置,或者从一个文件配置并在其中一个文件的代码中更新隔离级别)。 其中一个将具有“可序列化”的隔离级别,您可以通过它专门处理您的表。 隔离级别的属性是“hibernate.connection.isolation”。

在Spring中,使用@Transactional(isolation=Isolation.SERIALIZABLE)注释方法就足够了。

这是一个SQL解决方案,您可以使用Hibernate的本机SQL接口来简化。

insert into myTable (A, B, LAST_SEQ) 
select 'T', 'Y', 
(
  case 
    when (select max(LAST_SEQ) from myTable where A = 'T' and B = 'Y') is null then 1
    else (select 1 + max(LAST_SEQ) from myTable where A = 'T' and B = 'Y')
  end
);

希望能帮助到你。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM