如何在分布式环境中预取Oracle序列ID

时间:2020-03-05 18:47:37  来源:igfitidea点击:

我有一个在5个应用程序服务器上运行的分布式Java应用程序。所有服务器都使用在第六台计算机上运行的相同Oracle 9i数据库。

应用程序需要从序列中预取一批100个ID。在单线程,非分布式环境中,这样做相对容易,我们可以发出以下查询:

select seq.nextval from dual;
alter sequence seq increment by 100;
select seq.nextval from dual;

第一个选择获取应用程序可以使用的第一个序列ID,第二个选择返回可以使用的最后一个序列ID。

在多线程环境中,事情变得更加有趣。我们无法确定在第二个线程选择之前,另一个线程不会再次将序列增加100。我们可以通过同步Java端的访问来解决此问题,我们只需让一个线程一次开始获取ID。

当我们无法同步时,情况变得非常困难,因为应用程序的某些部分无法在同一JVM上运行,甚至无法在同一台物理计算机上运行。我在论坛上发现了一些参考资料,也有其他参考资料在解决此问题方面也有问题,但是没有一个答案真正有效,更不用说是合理的了。

社区可以为这个问题提供解决方案吗?

更多信息:

  • 我真的不能处理事务隔离级别。我使用JPA,更改不仅会影响预取查询,而且会影响整个应用程序,这对我来说是不可接受的。
  • 在PostgreSQL上,我可以执行以下操作:选择setval('seq',nextval('seq')+ n-1)
  • 当我们可以使用固定的增量值(在我的情况下完全可以接受)时,Matthew的解决方案起作用。但是,当我们不想固定增量的大小而是想要动态调整它时,是否有解决方案?

解决方案

回答

为什么不总是将序列始终增加100?每个"下一个"给我们100个序列号以供使用

SQL> create sequence so_test start with 100 increment by 100 nocache;

Sequence created.

SQL> select so_test.nextval - 99 as first_seq, so_test.currval as last_seq from dual;

 FIRST_SEQ   LAST_SEQ
---------- ----------
         1        100

SQL> /

 FIRST_SEQ   LAST_SEQ
---------- ----------
       101        200

SQL> /

 FIRST_SEQ   LAST_SEQ
---------- ----------
       201        300

SQL>

关于示例的注释。注意DDL。它将产生隐式提交

DDL产生的提交示例

SQL> select * from xx;

no rows selected

SQL> insert into xx values ('x');

1 row created.

SQL> alter sequence so_test increment by 100;

Sequence altered.

SQL> rollback;

Rollback complete.

SQL> select * from xx;

Y
-----
x

SQL>

回答

马修在这里有正确的方法。我认为,应用程序每次使用后都会重置序列的当前值是非常不寻常的。将增量大小设置为我们需要的任何其他常规方法。

同样,这种方式更有效。在Oracle中,从序列中选择nextval是高度优化的操作,而运行ddl来更改序列则要昂贵得多。

我想这并不能真正回答我们所编辑问题中的最后一点...

回答

为什么首先需要获取序列ID?在大多数情况下,我们将插入表中并返回ID。

insert into t (my_pk, my_data) values (mysequence.nextval, :the_data)
returning my_pk into :the_pk;

听起来我们正在尝试预先优化处理。

如果我们确实需要预提取ID,则只需调用序列100次即可。序列的全部要点是它管理编号。我们不应该假设我们可以获得100个连续的数字。

回答

因为当我们不希望固定大小增加时,序列并不是我们真正想要的,它们真正保证的是,我们将获得一个唯一的数字,该数字始终大于最后一个数字。总是有可能最终会出现差距,并且我们不能真正安全或者有效地实时调整增量。

我真的无法想到必须做这种事情的任何情况,但是最简单的方法可能就是将"当前"数字存储在某个地方,并根据需要对其进行更新。

这样的事情。

drop table t_so_test;

create table t_so_test (curr_num number(10));

insert into t_so_test values (1);
create or replace procedure p_get_next_seq (inc IN NUMBER, v_next_seq OUT NUMBER) As
BEGIN
  update t_so_test set curr_num = curr_num + inc RETURNING curr_num into v_next_seq;
END;
/

SQL> var p number;
SQL> execute p_get_next_seq(100,:p);

PL/SQL procedure successfully completed.

SQL> print p;

         P
----------
       101

SQL> execute p_get_next_seq(10,:p);     

PL/SQL procedure successfully completed.

SQL> print p;

         P
----------
       111

SQL> execute p_get_next_seq(1000,:p);

PL/SQL procedure successfully completed.

SQL> print p;

         P
----------
      1111

SQL>