用select max()然后插入max()+ 1

时间:2016-08-18 14:15:42

标签: java spring oracle hibernate concurrency

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

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 for update时,执行select max()或其他一些机制来锁定我们关心的行(与A和B匹配的行)。这将阻止其他正在尝试查找最大值的进程,然后我们可以插入行,当txn提交时,将允许等待的选择进行选择。如果表中目前没有与A和B匹配的行,我不确定这是如何工作的。
  3. 创建一个执行max select并以原子方式插入的存储过程。如果可能的话,我想避免存储过程。
  4. 在Hibernate中,有没有办法插入(select max() + 1)并从插入一个语句的列中返回值?

4 个答案:

答案 0 :(得分:0)

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

首先,我节省了插入时间。

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之后继续增长,这可能不起作用。

答案 1 :(得分:0)

如何使用聚合表:

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

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

答案 2 :(得分:0)

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

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

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

在Spring中,只需使用@Transactional(isolation=Isolation.SERIALIZABLE)注释方法即可。

答案 3 :(得分:0)

这是一个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
);

希望它有所帮助。