简体   繁体   English

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

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

I have a table that has a few columns and a sequence that increments for the value of those columns. 我有一个表有一些列和一个序列,该列为这些列的值递增。 For (a simplified) example: 对于(简化)示例:

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

I have multiple processes that will be using a Spring application with Hibernate and will accept both A and B, select the max value for A and B, and then insert max + 1 . 我有多个进程将使用带有Hibernate的Spring应用程序并接受A和B,选择A和B的最大值,然后插入max + 1

So, for the example above, if someone provided A = z and B = x, we'd select, get 1 and insert z, x, 2. 所以,对于上面的例子,如果有人提供A = z和B = x,我们选择,得到1并插入z,x,2。

If someone provided A = z and B = z (no existing rows match), the process would query and find no max (or a max of zero) and then insert z, z, 1. 如果某人提供了A = z且B = z(没有现有行匹配),则该过程将查询并找不到最大值(或最大值为零),然后插入z,z,1。

Assume that a simple sequence will not work here because the column size for sequence isn't going to be large enough to allow one sequence across all values of A and B and because the actual problem is more complex than the example provided above. 假设一个简单的序列在这里不起作用,因为序列的列大小不足以允许一个序列跨越A和B的所有值,并且因为实际问题比上面提供的示例更复杂。

I can think of four solutions to this issue that may work but I'm wondering if there's a more standard way to do this and I'm not sure how any of these methods can be implemented with Hibernate and without using custom Oracle features: 我可以想到这个问题的四个解决方案可能有用,但我想知道是否有更标准的方法来实现这一点,我不确定如何使用Hibernate实现这些方法,并且不使用自定义Oracle功能:

  1. Perform the select max() , attempt to insert. 执行select max() ,尝试插入。 If there's a concurrency issue, one of the threads will fail and the code will be written to retry as many times as needed until the insert works. 如果存在并发问题,其中一个线程将失败,并且代码将被编写为根据需要重试多次,直到插入工作。 With Hibernate, this may mean we'd have to manually flush in the middle of our transaction. 使用Hibernate,这可能意味着我们必须在事务中间手动刷新。
  2. Perform a select for update or some other mechanism to lock the rows we care about (the ones that match A and B) when we're doing the initial select max() . 当我们执行初始select max()时,执行select for update或其他一些机制来锁定我们关心的行(匹配A和B的行select max() This will block other processes that are also trying to find the max and we can then insert our row and when the txn commits, the selects that are waiting will then be allowed to select. 这将阻止其他正在尝试查找最大值的进程,然后我们可以插入行,当txn提交时,将允许等待的选择进行选择。 I'm not sure how this would work if there are currently no rows in the table that match A and B, though. 我不确定如果表中当前没有与A和B匹配的行,这将如何工作。
  3. Create a stored procedure that does the max select and insert atomically. 创建一个执行max select并以原子方式插入的存储过程。 I'd like to avoid stored procs if possible, though. 不过,如果可能的话,我想避免存储过程。
  4. In Hibernate, is there a way to do an insert ( select max() + 1 ) and return the value from a column that was inserted in one statement? 在Hibernate中,有没有办法进行插入( select max() + 1 )并从插入一个语句的列中返回值?

Not sure if this will work for you. 不确定这是否适合你。 But that is how I would do it, because trying to keep concurrency can be complicated. 但这就是我要做的,因为试图保持并发可能很复杂。

First I save the time of insert. 首先,我节省插入时间。

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

Then your query become 然后你的查询成为

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`

EDIT: This may not work if you want delete and keep growing after the last seq. 编辑:如果您想删除并在最后一个seq后继续增长,这可能不起作用。

What about having aggregation table: 那么聚合表怎么样:

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

Then you can easily use row-level lock to safely generate records in 1st table. 然后,您可以轻松使用行级锁定在第一个表中安全地生成记录。

You want your transactions doing this read-increment-update/insert to be serializable (as in "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"), that isolation level makes the DB try to find an order in which transactions would be run sequentially and get the same results; 您希望执行此read-increment-update / insert的事务可序列化(如“SET TRANSACTION ISOLATION LEVEL SERIALIZABLE”),该隔离级别使DB尝试查找顺序运行事务的顺序并获得相同的顺序结果; if it can't, it doesn't allow some of them to pass. 如果它不能,它不允许其中一些通过。 I tried your scenario in Postgres CLI, and it won't allow both inserts (error on commit) or conflicting updates (error on second update finished, transaction aborted); 我在Postgres CLI中尝试了你的场景,它不允许插入(提交错误)或冲突更新(第二次更新完成错误,事务中止); I think Oracle behaves similarly although details may vary. 我认为Oracle的行为类似,但细节可能会有所不同。 That works even if the criterion for select is different in different transactions: for example first transaction selects by A, second by B, both do changes and those changes would affect any of the previous selects anyhow — you get an error. 即使select的标准在不同的事务中是不同的,这也是有效的:例如,第一个事务由A选择,第二个由B选择,两者都进行更改,这些更改无论如何都会影响任何先前的选择 - 您会收到错误。 And then, I guess, retry. 然后,我猜,重试。 Watch out for possible deadlocks associated with SERIALIZABLE isolation which will result in errors (but you will already have retries so that's fine). 注意与SERIALIZABLE隔离相关的可能死锁,这会导致错误(但是你已经有了重试,所以没关系)。

By the way, for this sort of update correctness REPEATABLE READ isolation level is enough, but not for inserts. 顺便说一下,对于这种更新的正确性,REPEATABLE READ隔离级别就足够了,但不适用于插入。

As for how to have different transaction isolation levels in Hibernate (you probably don't want all transactions to be serializable as it affects performance), I can only think about having two session factories configured from two config files (or manually in the code, or configured from one file and having isolation level updated in the code for one of them). 至于如何在Hibernate中使用不同的事务隔离级别(您可能不希望所有事务都可序列化,因为它会影响性能),我只能考虑从两个配置文件配置两个会话工厂(或者在代码中手动配置,或者从一个文件配置并在其中一个文件的代码中更新隔离级别)。 One of them will have "serializable" isolation level and you would work with your table through it exclusively. 其中一个将具有“可序列化”的隔离级别,您可以通过它专门处理您的表。 The property for isolation level is "hibernate.connection.isolation". 隔离级别的属性是“hibernate.connection.isolation”。

In Spring it's enough to annotate a method with @Transactional(isolation=Isolation.SERIALIZABLE) . 在Spring中,使用@Transactional(isolation=Isolation.SERIALIZABLE)注释方法就足够了。

Here is a SQL solution for you, you can use Hibernate's native SQL interface for simplicity. 这是一个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
);

Hope it helps. 希望能帮助到你。

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

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