题
我有一个分布式 Java 应用程序在 5 个应用程序服务器上运行。这些服务器都使用在第 6 台机器上运行的相同 Oracle 9i 数据库。
应用程序需要从序列中预取一批 100 个 ID。在单线程、非分布式环境中相对容易做到,您只需发出以下查询:
select seq.nextval from dual;
alter sequence seq increment by 100;
select seq.nextval from dual;
第一个选择获取应用程序可以使用的第一个序列 ID,第二个选择返回最后一个可以使用的序列 ID。
在多线程环境中事情变得更加有趣。您无法确定在第二次 select 之前另一个线程不会再次将序列增加 100。这个问题可以通过在 Java 端同步访问来解决 - 一次只让一个线程开始获取 ID。
当您无法同步时,情况会变得非常困难,因为应用程序的某些部分不在同一个 JVM 上运行,甚至不在同一台物理机器上运行。我在论坛上发现了一些参考资料,其他人也有解决这个问题的问题,但没有一个答案真正有效,更不用说合理了。
社区可以为这个问题提供解决方案吗?
更多信息:
- 我无法真正使用事务隔离级别。我使用 JPA,更改会影响整个应用程序,而不仅仅是预取查询,这对我来说是不可接受的。
在 PostgreSQL 上我可以执行以下操作:
选择 setval('seq', nextval('seq') + n - 1 )
当您可以使用固定增量值时,Matthew 的解决方案就有效(在我的情况下这是完全可以接受的)。但是,当您不想固定增量的大小,而是想动态调整它时,有没有解决方案?
解决方案
为什么不让序列始终以 100 递增呢?每个“nextval”为您提供 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>
其他提示
为什么首先需要获取序列 ID?在大多数情况下,您会插入表并返回 ID。
insert into t (my_pk, my_data) values (mysequence.nextval, :the_data)
returning my_pk into :the_pk;
听起来您正在尝试预先优化处理。
如果您确实需要预取 ID,则只需调用该序列 100 次即可。序列的全部要点在于它管理编号。您不应该假设您可以获得 100 个连续的数字。
马太在这里有正确的方法。在我看来,应用程序在每次使用后重置序列的当前值是非常不寻常的。更传统的做法是预先将增量大小设置为您需要的任何值。
而且,这种方式的性能要高得多。从序列中选择 nextval 是 Oracle 中高度优化的操作,而运行 ddl 来更改序列的成本要高得多。
我想这并没有真正回答您编辑的问题中的最后一点......
因为当您不需要固定大小的增量时,序列并不是您真正想要的,它们真正保证的是您将获得一个始终比您获得的最后一个更大的唯一数字。总有可能最终会出现间隙,并且您无法真正安全或有效地动态调整增量。
我真的想不出我必须做这种事情的任何情况,但最简单的方法可能是将“当前”数字存储在某处并根据需要进行更新。
像这样的东西。
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>