分散環境で Oracle シーケンス ID をプリフェッチする方法
質問
5 つのアプリケーション サーバーで実行されている分散 Java アプリケーションがあります。すべてのサーバーは、6 台目のマシンで実行されている同じ Oracle 9i データベースを使用します。
アプリケーションは、シーケンスから 100 ID のバッチをプリフェッチする必要があります。シングルスレッドの非分散環境では比較的簡単に実行でき、次のクエリを発行するだけです。
select seq.nextval from dual;
alter sequence seq increment by 100;
select seq.nextval from dual;
最初の選択はアプリケーションが使用できる最初のシーケンス ID をフェッチし、2 番目の選択は使用できる最後のシーケンス ID を返します。
マルチスレッド環境では、物事はさらに面白くなります。2 回目の選択の前に、別のスレッドがシーケンスを再び 100 増加させないことを確信することはできません。この問題は、Java 側でアクセスを同期することで解決できます。一度に 1 つのスレッドのみが ID の取得を開始できるようにします。
アプリケーションの一部が同じ JVM 上、さらには同じ物理マシン上で実行されていないために同期できない場合、状況は非常に困難になります。フォーラムで、他の人もこの問題の解決に問題を抱えているという参考文献をいくつか見つけましたが、合理的であることは言うまでもなく、実際に機能する答えはどれもありません。
コミュニティはこの問題の解決策を提供できるでしょうか?
さらに詳しい情報:
- トランザクション分離レベルを実際に試すことはできません。私は JPA を使用していますが、この変更はプリフェッチ クエリだけでなくアプリケーション全体に影響するため、それは私にとって受け入れられません。
PostgreSQL では次のことができます。
select 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>