2016-08-18 90 views
1

我有一个表,有几列和一个序列,为这些列的值增加。对于(简化的)例子:休眠并发问题与选择max(),然后插入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 for update或其他一些机制来锁定我们关心的行(与A和B匹配的行),当我们执行初始的select max()时。这将阻止其他正在尝试查找max的进程,然后我们可以插入我们的行,并且当txn提交时,正在等待的选择将被允许选择。但我不确定如果表中当前没有与A和B匹配的行,这将如何工作。
  3. 创建一个存储过程,它自动执行max select和insert操作。尽管如此,我想避免存储过程。
  4. 在Hibernate中,有没有办法做一个插入(select max() + 1)并返回插入到一个语句中的列的值?

回答

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` 

编辑:此,如果你想删除,并保留最后以次增长后可能无法正常工作。

+0

感谢您的建议。我们可能不会在此表中删除任何内容。 'partition by'需要原生SQL,我想避免这种情况。 –

+0

你太挑剔了,首先不是storeproc,现在不用原生sql。 Oracle有'row_number()结束(分区' –

+0

你检查过这个吗?http://stackoverflow.com/questions/3616321/concurrent-updates-handling-in-hibernate?rq=1 –

0

什么有汇总表:

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

然后你就可以很容易地使用行级锁安全产生第一个表中的记录。

+0

我们可以做到这一点,但我们不不知道A和B的所有组合(并且在实际场景中会涉及更多的列),所以我们不容易预先填充A和B(或A,B,C等)的所有可能值, –

+0

你不必预先填充它,你可以合并到这个表中,然后简单地读取LAST_SEQ的结果值。但这不能通过JQL来完成,hibernate有意不支持MERGE – ibre5041

+0

你不能行级别如果没有行锁定,那么会发生什么情况如果两个线程从表中选择,没有结果,然后尝试同时插入第一行? –

0

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

顺便说一下,对于这种更新正确性,REPEATABLE READ隔离级别就足够了,但不适用于插入。关于如何在Hibernate中有不同的事务隔离级别(你可能不希望所有的事务都是可序列化的,因为它会影响性能),我只能考虑从两个配置文件(或手动配置)中配置两个会话工厂在代码中,或者从一个文件进行配置,并在其中一个文件的代码中更新了隔离级别)。其中一个将具有“可序列化”的隔离级别,并且您将独占地处理您的表格。隔离级别的属性是“hibernate.connection.isolation”。

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

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 
); 

希望它有帮助。

+0

谢谢。这可能工作,但我需要得到一些标识符,称之为LAST_SEQ,它会告诉我如何读回我插入的行。 –