この言葉を理解するのを手伝ってくれませんか?
バルク バインドがないと、PL/SQL は、挿入、更新、または削除されるレコードごとに SQL ステートメントを SQL エンジンに送信し、パフォーマンスを低下させるコンテキスト スイッチにつながります。
Oracle 内には、SQL 仮想マシン (VM) と PL/SQL VM があります。ある 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/SQL VM から SQL VM に 1 行のデータを送信するためのコンテキスト シフトのコストが発生し、SQL にINSERT
ステートメントの実行を要求し、別のコンテキスト シフトを PL/SQL に戻すコストが発生しています。
100 万行の場合source_table
、それは 400 万回のコンテキスト シフトであり、コードの経過時間のかなりの部分を占める可能性があります。一方、100 の a を実行するBULK COLLECT
と、LIMIT
コンテキストのコストが発生するたびに、SQL VM から 100 行のデータを取得して PL/SQL のコレクションに入れることで、コンテキスト シフトの 99% を排除できます。そこでコンテキストシフトが発生するたびに、宛先テーブルに100行をシフトして挿入します。
コードを書き直して一括操作を利用できる場合
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
そして、挿入を行うたびに、1 セットのコンテキスト シフトで 100 行を挿入しています。100 万行ある場合source_table
、これは、400 万回のコンテキスト シフトから 40,000 回のコンテキスト シフトになったことを意味します。たとえば、コンテキスト シフトがコードの経過時間の 20% を占めている場合、経過時間の 19.8% を排除しています。
のサイズを大きくしてLIMIT
コンテキスト シフトの数をさらに減らすことはできますが、収穫逓減の法則にすぐに突き当たります。100 ではなく 1000を使用するLIMIT
と、99% ではなく 99.9% のコンテキスト シフトが排除されます。ただし、これはコレクションが 10 倍の PGA メモリを使用していたことを意味します。また、この仮説の例では、経過時間が 0.18% 増加するだけです。追加のコンテキスト シフトを排除することで節約できる時間よりも、使用している追加のメモリが多くの時間を追加するポイントにすぐに到達します。一般に、LIMIT
100 から 1000 の間のどこかがスイート スポットである可能性があります。
もちろん、この例では、すべてのコンテキスト シフトを排除し、単一の SQL ステートメントですべてを実行する方が効率的です。
INSERT INTO dest_table( col1, col2, ... , colN )
SELECT col1, col2, ... , colN
FROM source_table;
そもそもPL/SQLに頼るのは、SQLで合理的に実装できないソース表からのデータ操作を行っている場合にのみ意味があります。
さらに、この例では意図的に明示カーソルを使用しました。暗黙カーソルを使用している場合、最近のバージョンの Oracle では、100のBULK COLLECT
aを暗黙的に使用する利点が得られます。LIMIT
別の StackOverflow の質問があります。この質問では、暗黙カーソルと明示カーソルの相対的なパフォーマンス上の利点と、これらの特定のしわについて詳しく説明する一括操作について説明しています。
私がこれを理解しているように、関係する2つのエンジン、PL/SQLエンジンとSQLエンジンがあります。一度に1つのエンジンを使用するクエリを実行する方が、2つのエンジンを切り替えるよりも効率的です。
例:
INSERT INTO t VALUES(1)
SQLエンジンによって処理されます
FOR Lcntr IN 1..20
END LOOP
PL/SQLエンジンによって実行されます
上記の2つのステートメントを組み合わせて、INSERTをループに入れると、
FOR Lcntr IN 1..20
INSERT INTO t VALUES(1)
END LOOP
Oracleは、反復ごとに2つのエンジンを切り替えます。この場合、実行全体でPL/SQLエンジンを使用するBULKINSERTをお勧めします。