Oracle内には、SQL仮想マシン(VM)とPL /SQLVMがあります。あるVMから別のVMに移動する必要がある場合、コンテキストシフトのコストが発生します。個々に、これらのコンテキストシフトは比較的高速ですが、行ごとの処理を行う場合、コードが費やしている時間のかなりの部分を占めるようになる可能性があります。一括バインドを使用する場合、1つのコンテキストシフトで複数のデータ行を1つのVMから別のVMに移動し、コンテキストシフトの数を大幅に減らして、コードを高速化します。
たとえば、明示カーソルを考えてみましょう。このようなものを書いたら
DECLARE
CURSOR c
IS SELECT *
FROM source_table;
l_rec source_table%rowtype;
BEGIN
OPEN c;
LOOP
FETCH c INTO l_rec;
EXIT WHEN c%notfound;
INSERT INTO dest_table( col1, col2, ... , colN )
VALUES( l_rec.col1, l_rec.col2, ... , l_rec.colN );
END LOOP;
END;
その後、フェッチを実行するたびに、私は
- PL /SQLVMからSQLVMへのコンテキストシフトの実行
- 次の行のデータを生成するためにカーソルを実行するようにSQLVMに要求する
- SQLVMからPL/SQL VMに戻る別のコンテキストシフトを実行して、単一行のデータを返します
そして、行を挿入するたびに、同じことをします。 1行のデータをPL/SQLVMからSQLVMに送信し、SQLにINSERT
を実行するように要求するために、コンテキストシフトのコストが発生します。 ステートメントを作成し、別のコンテキストのコストが発生すると、PL/SQLに戻ります。
source_table
の場合 100万行あります。これは400万のコンテキストシフトであり、コードの経過時間のかなりの部分を占める可能性があります。一方、BULK COLLECT
を実行する場合 LIMIT
を使用 100の場合、コンテキストシフトのコストが発生するたびにSQLVMからPL/ SQLのコレクションに100行のデータを取得し、毎回宛先テーブルに100行を挿入することで、コンテキストシフトの99%を排除できます。そこでコンテキストシフトが発生します。
一括操作を利用するようにコードを書き直すことができれば
DECLARE
CURSOR c
IS SELECT *
FROM source_table;
TYPE nt_type IS TABLE OF source_table%rowtype;
l_arr nt_type;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_arr LIMIT 100;
EXIT WHEN l_arr.count = 0;
FORALL i IN 1 .. l_arr.count
INSERT INTO dest_table( col1, col2, ... , colN )
VALUES( l_arr(i).col1, l_arr(i).col2, ... , l_arr(i).colN );
END LOOP;
END;
これで、フェッチを実行するたびに、1セットのコンテキストシフトを使用して100行のデータをコレクションに取得します。そして、FORALL
を実行するたびに 挿入、コンテキストシフトの単一のセットで100行を挿入しています。 source_table
の場合 100万行あります。これは、400万のコンテキストシフトから40,000のコンテキストシフトに移行したことを意味します。たとえば、コンテキストシフトがコードの経過時間の20%を占めている場合、経過時間の19.8%を排除しました。
LIMIT
のサイズを増やすことができます コンテキストシフトの数をさらに減らすために、しかしあなたはすぐに収穫逓減の法則にぶつかります。 LIMIT
を使用した場合 100ではなく1000の場合、99%ではなく99.9%のコンテキストシフトを排除します。ただし、コレクションが10倍多くのPGAメモリを使用していたことを意味します。また、架空の例では、経過時間が0.18%長くなるだけです。使用している追加のメモリが、追加のコンテキストシフトを排除することにより、節約するよりも多くの時間を追加するポイントに非常にすばやく到達します。一般的に、LIMIT
100から1000の間のどこかがスイートスポットになる可能性があります。
もちろん、この例では、すべてのコンテキストシフトを排除し、すべてを1つのSQLステートメントで実行する方が効率的です。
INSERT INTO dest_table( col1, col2, ... , colN )
SELECT col1, col2, ... , colN
FROM source_table;
SQLで合理的に実装できないソーステーブルからのデータのある種の操作を行っている場合にのみ、そもそもPL/SQLに頼るのは理にかなっています。
さらに、私の例では意図的に明示カーソルを使用しました。暗黙カーソルを使用している場合、最近のバージョンのOracleでは、BULK COLLECT
の利点が得られます。 LIMIT
を使用 暗黙的に100の。別のStackOverflowの質問があり、これらの特定のしわについてより詳細に説明する一括操作による暗黙カーソルと明示カーソルの相対的なパフォーマンス上の利点について説明しています。