Oracle PLSQL ストアド プロシージャのパフォーマンス最適化方法

Oracle PLSQL ストアド プロシージャのパフォーマンス最適化方法

1. パフォーマンスに影響を与える一般的な原因

1. ストアド プロシージャを使用し、PL/SQL 匿名ブロックの使用を避けてください。

ストアド プロシージャが作成されると、Oracle はそのストアド プロシージャに対して構文解析を実行し、コンパイルされた形式でデータベースに保存します。クライアントがストアド プロシージャを呼び出すときは、呼び出し命令を送信するだけで済みます。これにより、匿名ブロックによる大量のデータの送信が回避されます。ソースコードをインターネット上に公開することでネットワークコストを削減し、通信負荷を軽減し、作成時に一度コンパイルするだけなのでプログラム実行のパフォーマンスが向上します。

2. 共有 SQL ステートメントを作成する

Oracle が SQL ステートメントを実行するとき、最初の解析の後、システム グローバル領域 SGA にある共有プールに SQL ステートメントが配置されます。このメモリ領域はすべてのデータベース ユーザーが共有できるため、SQL ステートメントを実行するときは、たとえば次のようになります。 PL/SQL 文内のカーソルが SQL 文を実行するときに、それが以前に実行された文と同じであることを Oracle が検出すると、解析された文が使用され、最適な実行パスが使用されます。

Oracle は SQL ステートメントを実行するとき、必ず最初に共有メモリ領域で同じ SQL ステートメントを検索しますが、Oracle は単純なテーブルのみをキャッシュするため、複数テーブルの接続クエリには適していません。

SELECT * FROM EMP;

EMP から SELECT * を選択します。

Emp から * を選択します。

SELECT * FROM EMP;

このタイプの SQL ステートメントを回避するには、SQL ステートメントを作成するときに、キーワード、予約語は大文字で、ユーザー宣言の識別子は小文字で、一貫した大文字と小文字の規則を使用することに注意する必要があります。これらの規則に従って、ステートメントを共有プール内のステートメントと一貫して処理できるため、実行パフォーマンスの向上に役立ちます。

3. BINARY_INTEGER と PLS_INTEGER を使用して整数を宣言します

PLSQL プログラミングで変数の型を宣言する場合は、常に BINARY_INTEGER と PLS_INTEGER を使用して、数値型に依存しすぎないようにする必要があります。これは、前者の方がパフォーマンスが速いためです。

4. プロセス内で大きなデータパラメータを渡す場合は、NOCOPY コンパイルプロンプトを使用します。

プロシージャまたは関数を作成するとき、IN モードは常にポインターを渡しますが、OUT および IN OUT は値のコピーを渡します (値渡しとも呼ばれます)。大容量のパラメータの受け渡しが関係する場合、パフォーマンスが大幅に低下します。現時点では、NOCOPY コンパイル ヒントを使用してパラメータを参照渡しすることを検討する必要があります。パラメータのサイズが大きいほど、その影響はより顕著になります。たとえば、次のようになります。プロシージャが IN OUT
型であると仮定します。パラメータはデフォルトで値によって渡されます。次の例は、このプロセスを複数回呼び出し、大きなインデックス テーブル パラメータを渡す方法を示しています。NOCOPY を使用しない場合、パフォーマンスが大幅に低下します。

NOCOPY を使用してパフォーマンスを向上させる

declare
type test_tb1 is table of pls_integer index by pls_integer;  --定义索引表类型
test_tb1 test_tb1_type;   --定义索引表类型的变量
--定义内嵌子程序,在IN OUT 参数中使用NOCOPY提示来按引用传递
procedure test (arg_cnt in pls_integer,arg_tb1 in out nocopy test_tb1_type)
is
begin
for cnt_test in test_tb1.first .. arg_tb1.last --依循环索引表
loop
arg_tb1 (cnt_test):=arg_tb1 (cnt_test) + arg_cnt;
end loop;	--为形式参数表赋值
end;
begin
for cnt in 0 .. 10000
loop
test_tb1 (cnt) := cnt;
end loop;
for cnt in 0 .. 10000
loop
test (cnt,test_tb1);
end loop;
end;

5.return を使用して戻り値を取得します

DML ステートメントを使用してオブジェクト行データを処理するときに、行の戻り値を取得する場合は、SQL 実行の数を減らし、実行効率を向上させるために、常に return 句を使用する必要があります。

insert into … value (…) :col1 にcol1 を返す;
update … set … :col1 にcol1 を返す;
delete … :col1 にcol1 を返す;
return を使用すると、複数列のデータを返すだけでなく、データを返して保存することもできます。中央:
col1、col2 を :col1、:col2 に返し、
col1 を :col1_array に返します。

6. 動的 SQL ステートメントの使用を避ける

動的SQL文はプログラミングに便利ですが、動的SQL文を過度に使用するとPLSQLアプリケーションのパフォーマンスが大幅に低下するため、必要に応じて静的SQL文の使用を常に検討する必要があります。動的 SQL ステートメントを使用する必要がある場合は、dbms_sql を使用する代わりにローカル動的 SQL ステートメントを使用すること、つまり、即時実行または open for を使用することを常に選択する必要があります。これは、dbms_sql はコードの記述が複雑であるだけでなく、パフォーマンスも劣るためです。ローカルの動的 SQL ステートメント。

7. 一括バッチ処理を試してみる

操作に大量のデータが含まれる場合は、大量のデータを一度に処理することでパフォーマンスを向上させることができます。たとえば、データをインデックス テーブル、ネストしたテーブル、可変長配列に配置し、バッチ処理ステートメントを使用できます。 forall やBulk Collect into など、一度に大量のデータを処理してパフォーマンスを向上させます。

Bulk Collect into ステートメントを使用して、emp テーブル内のすべてのデータを一度にインデックス テーブル変数に挿入します。データ量が特に大きい場合、パフォーマンスが大幅に向上します。

バッチ処理を使用してすべてのデータを一度に取得する

declare
type emp_tb1 is table of emp%ROWTYPE index by pls_integer; --定义索引表类型
        emp_tb1	emp_tb1_type;
cursor	emp_cur
is
select * from emp;
begin
open emp_cur;
fetch emp_cur
bulk collect into emp_tb1;
close emp_cur;
end;

Bulk Collect into 句を使用すると、すべてのカーソル データがインデックス テーブル変数に一度に抽出されるため、プログラムの実行パフォーマンスが向上し、コードの記述量が節約されます。したがって、可能な限りバッチ処理を使用してデータ処理を完了する必要があります。

2. DBMS_PROFILER パッケージを使用する

1.DBMS_PROFILER パッケージをインストールします。

DBMS_PROFILER を使用する前に、インストールのために管理者としてデータベース システムに入る必要があります。
conn system/manager as sysdba;
desc dbms_profiler;
desc コマンドにより、dbms_profiler パッケージが存在しないことが示された場合は、次のコマンドを使用してインストールする必要があります:
sql>@?/rdbms/admin/profload.sql
desc dbms_profiler を再度実行します。このパッケージにはサブルーチン情報が含まれており、主に 2 つの関数が使用されています:
start_profiler プロファイラーを開始
stop_profiler プロファイルを停止

2. プロファイラー ソリューションを構成する

追跡情報とプロファイラー関連テーブルのシノニムを保存するユーザーを作成します。

123456 で識別されるユーザー プロファイラを作成します。
プロファイラーへの接続、リソースの付与。
profiler.plsql_profiler_runs のパブリック シノニム plsql_profiler_runs を作成します。
profiler.plsql_profiler_units のパブリック シノニム plsql_profiler_units を作成します。
profiler.plsql_profiler_data のパブリック シノニム plsql_profiler_data を作成します。
profiler.plsql_profiler_runnumber のパブリック シノニム plsql_profiler_runnumber を作成します。

3. プロファイラーテーブルを構成する

コンプロファイラー/123456

@?/rdbms/admin/proftab.sql

plsql_profile_runnumber の選択をパブリックに付与;
plsql_profiler_data の選択、挿入、更新、削除をパブリックに付与; plsql_profiler_units の選択、
挿入、更新、削除をパブリックに付与; plsql_profiler_runs の選択、挿入、更新、削除をパブリックに
付与; plsql_profile_runnumber に
保存されたプロファイラ実行情報
plsql_profiler_data は、各ユニットのプロファイラ情報を保存します。
plsql_profiler_units は、各ユニットの詳細データを保存します。
plsql_profiler_runs は、プロファイラ固有の実行番号のシーケンスを生成するために使用されます。

4. プロファイラーを実行して構成情報を取得します

プロセスを作成した後、プロファイラーを使用してプログラム コードをインストルメント化できます。
テストするプロセスを作成する

create table pro_tst_table (a int);
create or replace procedure sp_test
as
begin
for i in 1 .. 10000
loop
insert into pro_tst_table values(i);
end loop;
commit;
end;

dbms_profiler を使用してパッケージをテストする

declare
v_run_number	integer;
v_temp1	integer;
begin
--启动profiler
sys.DBMS_PROFILER.start_profiler(run_number => v_run_number);
--显示当前跟踪的运行序号(后面查询要用)
dbms_output.put_line('run_number:'||v_run_number);
--运行要跟踪的PLSQL
sp_test;
--停止profiler
sys.DBMS_PROFILER.stop_profiler;
end;

5. プロファイラーにクエリを実行して結果を取得する

SQL ステートメントを使用してこの実行の情報をクエリするには、まず plsql_profiler_runs をクエリして、この実行の基本情報を取得します: select runid, run_owner, run_date
, run_total_time from plsql_profiler_runs;
作成者はコードを 2 回実行したため、runid レコードが 2 つあります。 , ID 値はシリアル番号によって生成され、ID の最大値は最新の実行を示します。RUN_TOTAL_TIME は
実行時間を示します。2 つの実行の時間が大きく異なっていることがわかります。
このプロファイルのユニット情報は次のとおりです。 plsql_profiler_units テーブルをクエリすることで取得されます。
plsql_profiler_data テーブルをクエリすると、行番号とユニット番号に基づいて、実行されたストアド プロシージャの各行の統計情報を取得できます。

3. DBMS_TRACE パッケージを使用する

たとえば、サブルーチンの実行順序を確認する場合は、DBMS_TRACE パッケージを使用できます。このパッケージの使用方法は、DBMS_PROFILER の使用方法と似ています。大きな違いの 1 つは、dbms_trace が追跡する必要があるイベント (呼び出し、例外、イベントなど) を設定できることです。 SQL、さらには各 PLSQL コードで発生する可能性のあるイベントも実行します。この情報を利用すると、バックグラウンド プログラム プロセスの異常を迅速に特定できます。

パッケージには 2 つの機能があります。

set_plsql_trace: トレース統計の収集をオンにします。

clear_plsql_trace: トレース統計の収集を停止します

1. DBMS_TRACE を構成して使用する

これを使用する前に、dbms_trace で使用されるデータ テーブルを構成し、すべてのユーザーがこれらのテーブルにデータを書き込めるようにする必要があります。

システム/マネージャーを sysdba として接続

@?/rdbms/admin/tracetab.sql

スクリプトは 2 つのテーブルとシーケンスを作成します

plsql_trace_runs テーブル: 各トレース情報を記録するために使用されます。

plsql_trace_events テーブル: すべてのトレースの詳細データを記録するために使用されます。

plsql_trace_runnumber シーケンス: 一意の実行番号を生成するために使用されるシーケンス。

2. 必要なテーブルが正常に作成されたら、パブリック ロールを持つユーザーが対応するテーブルで操作できるように、対応するテーブルのシノニムを作成し、それらにアクセス可能な権限を割り当てる必要があります。

sys.plsql_trace_runs のパブリック シノニム plsql_trace_runs を作成または置換します。

sys.plsql_trace_events のパブリック シノニム plsql_trace_events を作成または置換します。

sys.plsql_trace_runnumber のパブリック シノニム plsql_trace_runnumber を作成または置換します。

plsql_trace_events の select、insert、update、delete を public に許可します。

plsql_trace_runs の select、insert、update、delete を public に許可します。

plsql_trace_runnumber の選択を public に許可します。

dbms_trace テスト プログラムを作成する

create or replace procedure do_something (p_times in number)
as
1_dummy	number;
begin
for i in 1 .. p_times
loop
select 1_dummy + 1 into 1_dummy from dual;
end loop;
end;

dbms_trace トレース プログラムの使用

declare
1_result binary_integer;
begin
--跟踪所有的调用
dbms_trace.set_plsql_trace(dbms_trace.trace_all_calls);
do_something(100);
--停止PLSQL跟踪
dbms_trace.clear_plsql_trace;
--跟踪所有的sql语句
dbms_trace.set_plsql_trace(dbms_trace.trace_all_sql);
do_something(100);
--停止跟踪
dbms_trace.clear_plsql_trace;
--跟踪所有行数据
dbms_trace.set_plsql_trace(dbms_trace.trace_all_lines);
do_something(100);
dbms_trace.clear_plsql_trace;
end;

各呼び出しでは、最初に set_plsql_trace を使用してトレース・プロセスが開始されます。このプロセスのパラメータ DBMS_TRACE_trace_all_calls は、呼び出しまたは戻り値をトレースするために指定された定数です。

DBMS_TRACE パッケージのパッケージ仕様には、set_plsql_trace で使用できる定数のリストと詳細な説明が含まれています。

plsql_trace_runs テーブルをクエリして、各トレース情報を取得します。

4. PLSQLパフォーマンス最適化スキル

Oracle データベースが SQL ステートメントを実行するとき、Oracle のオプティマイザは、SQL ステートメントが最適なパフォーマンスで実行されることを保証するために、特定のルールに従って SQL ステートメントの実行パスを決定します。Oracle データベース システムで SQL ステートメントを実行するには、 Oracle では、複数のステップを実装する必要がある場合があります。これらの各ステップは、データベースから物理的にデータ行を取得したり、SQL ステートメントを作成するユーザーが使用できるように何らかの方法でデータ行を準備したりする場合があります。Oracle が実行するために使用するこれらのステップの組み合わせです。このステートメントは実行計画と呼ばれます。

Oracle は SQL ステートメントを実行するときに 4 つのステップを実行します。

SQL ステートメントの解析: 主に、共有プール内の同じ SQL ステートメントをクエリして、セキュリティ、SQL 構文、セマンティクスをチェックします。

実行計画の作成と実行:SQL文の実行計画の作成と実際のテーブルデータの取得を含みます。

結果セットを表示する: フィールド データの必要な並べ替え、変換、および再フォーマットをすべて実行します。

フィールド データの変換: 組み込み関数を通じて変換されたフィールドを再フォーマットして変換します。

SQL ステートメントの実行計画を確認します。たとえば、一部のサードパーティ ツールでは、最初に utlxplan.sql スクリプトを実行して Explain_plan テーブルを作成する必要があります。

@?/rdbms/admin/utlxplan.sql

Explain に autotrace を設定: SQL を実行し、実行計画のみを表示します。

統計に自動トレースを設定: SQL を実行し、実行統計のみを表示します。

set autotrace on: SQL を実行し、実行計画と統計情報を表示します。実行結果は表示されません。

set autotrace traceonly: 実行計画と統計情報のみを表示し、実行結果は表示しません。

set autotrace off: トレースの表示計画と統計をオフにします。

Explain に自動トレースを設定する

列名形式 a20;

select empno,ename from emp where empno=7369;

5. 実行計画を理解する

1. フル テーブル スキャン: この方法では、テーブル内のすべてのレコードが読み取られ、終了マークまで各データ ブロックが順番に読み取られます。大規模なデータ テーブルの場合、フル テーブル スキャンを使用するとパフォーマンスが低下しますが、場合によっては、たとえば、テーブル全体のデータ量に対するクエリ結果の割合が比較的高い場合、テーブル全体のスキャンはインデックス選択よりも優れた方法です。

2. ROWID 値による取得: 行の ROWID は、行が配置されているデータ ファイル、データ ブロック、およびブロック内の行の位置を示します。そのため、ROWID を介してデータにアクセスすると、ターゲット データをすばやく見つけることができます。 Oracle によってアクセスされる単一の行であり、データにアクセスする最速の方法です。

3. インデックス スキャン: まずインデックスを通じてオブジェクトの ROWID 値を検索し、次に ROWID 値を通じてテーブルから直接特定のデータを検索します。これにより、検索効率が大幅に向上します。

6. 結合クエリのテーブル順序

デフォルトでは、オプティマイザは実行計画を生成するコストベースのオプティマイザ CBO である all_rows 最適化メソッドを使用します。CBO メソッドは統計情報に基づいて実行計画を生成します。

統計情報には、テーブルのサイズ、行数、各行の長さなどの情報が含まれます。これらの統計情報は、最初はライブラリに存在しません。分析後に発見されました。多くの場合、期限切れの統計情報により、問題が発生します。オプティマイザがエラーを起こす可能性があるため、この情報は適時に更新する必要があります。

CBO モードでは、複数のテーブルに対して結合クエリを実行する場合、Oracle アナライザーは from 句内のテーブル名を右から左の順序で処理します。例えば:

emp_log a、emp b、dept c から a.empno、a.ename、c.deptno、c.dname、a.log_action を選択します

実行中、Oracle は最初に dept テーブルにクエリを実行し、dept テーブルでクエリされた行をデータ ソースとして使用して emp テーブルにシリアル接続して実行を継続します。したがって、dept テーブルはベース テーブルまたはドライバ テーブルとも呼ばれます。 。接続の順序がクエリの効率に大きな影響を与えるためです。したがって、複数テーブルの結合を扱う場合は、レコードの少ないテーブルをベース テーブルとして選択する必要があり、Oracle はソートとマージを使用して接続します。たとえば、最初に dept テーブルをスキャンし、次に dept テーブルを並べ替え、次に emp テーブルをスキャンし、最後に取得したすべてのレコードを最初のテーブルのレコードとマージします。

テーブル結合クエリが 3 つ以上ある場合は、ベース テーブルとしてクロス集計を選択する必要があります。クロス集計は他のテーブルから参照されるテーブルを指します。emp_log は dept テーブルと emp テーブルのクロス集計であるため、dept の内容と emp の内容の両方が含まれます。

emp b、dept c、emp_log a から a.empno、a.ename、c.deptno、c.dname、a.log_action を選択します。

7. where 条件の順序を指定します。

テーブルをクエリする場合、where 句内の条件の順序が実行パフォーマンスに影響することがよくあります。デフォルトでは、Oracle は where 句をボトムアップ順序で解析します。そのため、複数テーブルのクエリを処理する場合、テーブル間の接続は他の where 条件より前に書き込む必要がありますが、データ レコードをフィルタリングするための条件は where 句の末尾より前に記述する必要があります。これにより、データがフィルタリングされた後に接続処理を実行できるようになり、SQL ステートメントのパフォーマンスが向上します。

* 記号の使用を避ける

8. デコード機能を使用する

たとえば、emp テーブル内の部門番号 20 と部門番号 30 の従業員の数と給与概要をカウントするには、decode を使用しない場合は 2 つの SQL ステートメントを使用する必要があります。

select count( ),SUM(sal) from emp where deptno=20;
Union
select count(
),SUM(sal) from emp where deptno=30;

上記で 2 つのテーブル全体のスキャンが実行されました

decode ステートメントを使用すると、1 つの SQL クエリで同じ結果が得られ、2 行の結果が 1 行として表示されます。

select count (デコード (deptno,20,'X',NULL)) dept20_count,
count (デコード (deptno,30,'X',NULL)) dept30_count,
SUM (デコード (deptno,20,sal,NULL)) dept20_sal,
sum (decode (deptno,30,sal,NULL)) dept30_sal から emp;

上記ではテーブル全体のスキャンが 1 回だけ実行されました

decode 関数を柔軟に使用すると、group by 句や order by 句で decode 関数を使用したり、decode ブロック内に別の decode ブロックをネストしたりするなど、多くの予期しない結果が得られる可能性があります。

9. have の代わりに where を使用する

where 句と Hasting 句の両方でデータをフィルタリングできますが、Wh​​ere 句では count max min avg sum やその他の関数などの集計関数を使用できません。

例:


sum(sal ) > 1000 および (20,30) の deptno を持つempno,deptno により、emp グループから empno,deptno,sum(sal) を選択します。

Have 句では、部門番号 20 または 30 のレコードがフィルターで除外されます。実際、これにより、クエリですべての部門の従業員レコードが取得され、グループ化計算が実行され、最後に部門 20 と 30 のレコードがフィルターで除外されます。グループ分けの結果について。これは非常に非効率的です。最初に where 句を使用して部門番号 20 と 30 のレコードをフェッチし、次にフィルタリングするのが良いアルゴリズムです。

select empno,deptno,sum(sal) from emp where (20,30) の deptno は、
sum (sal) > 1000 を持つ empno,deptno でグループ化します。

10. OR の代わりに UNION を使用する

OR 演算する 2 つの列が両方ともインデックス列である場合は、パフォーマンスを向上させるために Union の使用を検討できます。

例: たとえば、emp テーブルでは、empno と ename の両方にインデックス列が作成されます。empno と ename の間で OR 演算クエリを実行する必要がある場合は、パフォーマンスを向上させるために、これら 2 つのクエリをユニオンに変更することを検討できます。

select empno、ename、job、sal from emp where empno > 7500 または ename LIKE 'S%';

11.UNIONを使用する

select empno,ename,job,sal from emp where empno > 7500
UNION
select empno,ename,job,sal from emp where ename LIKE 'S%';

ただし、この方法では、両方の列がインデックス付き列であることが保証されます。

どうしても OR ステートメントを使用する場合は、パフォーマンスを向上させるために、返されるレコードが最も少ないインデックス列をできるだけ先頭に書くようにする必要があります。たとえば、empno > 7500 はクエリよりも少ないレコードを返します。 ename の場合、OR ステートメントの前に置くとパフォーマンスが向上します。もう 1 つの提案は、単一のフィールド値に対して OR 計算を実行する場合、代わりに IN の使用を検討することです。

たとえば次のような

select empno、ename、job、sal from emp where deptno=20 OR deptno=30;

12. IN の代わりに存在するものを使用する

たとえば、シカゴの全従業員のリストをクエリするには、IN の使用を検討できます。

select * from emp where deptno IN (
select deptno from dept where loc='CHICAGO');

と置換する

select * from emp where names (
select deptno from dept where loc='CHICAGO');

not in と not Does の間で同じ置換ページが発生します。not in 句は内部ソートとマージを実行します。実際には、サブクエリ内のテーブルのフル テーブル スキャンが実行されるため、効率は低くなります。 use NOT IN の場合、Yingai は常に外部接続に変更するか、NOT EXISTS を考慮します。

select * from emp where deptno NOT IN (
select deptno from dept where loc='CHICAGO');

パフォーマンスを向上させるために、結合クエリを使用できます。

select a.* from emp a,dept b where a.deptno=b.deptno and b.loc <> 'CHICAGO';

最も効率的な

select a.* from emp a where NOT EXISTS (
select 1 from dept b where a.deptno =b.deptno and loc='CHICAGO');

13. 非効率的なPL/SQLプロセス制御文を避ける

PLSQL は論理式の値を処理するときに、ショート パスの計算方法を使用します。

declare
v_sal	number:=&sal;
v_job	varchar2(20):=&job;
begin
if (v_sal > 5000 ) OR (v_job = '销售')
then
dbms_output.put_line('符合匹配的OR条件');
end if;
end;

まず最初の条件を判定しますが、v_sal が 5000 より大きい場合、v_job 条件は判定されませんので、この短絡計算方法を柔軟に使用することでパフォーマンスを向上させることができます。常に低コストの判定文を最初に配置する必要があります。これにより、前の判定が失敗した場合、後続の高コストの文は実行されなくなり、PL/SQL アプリケーションのパフォーマンスが向上します。

たとえば、and 論理演算子の場合、左側と右側の演算が true の場合にのみ結果が true になります。前の結果の最初の操作が false の場合、2 番目の操作は実行されません。

declare
v_sal	number:=&sal;
v_job	varchar2(20):=&job;
begin
if (check_sal(v_sal) > 5000) AND (v_job = '销售') --判断执行条件
then
dbms_output.put_line('符合匹配的and条件');
end if;
end;

このコードにはパフォーマンス上の危険があります。check_sal にはいくつかのビジネス ロジック チェックが含まれます。check_sal 関数が最初に呼び出される場合、この関数は常に呼び出されます。したがって、パフォーマンスを考慮して、v_job の判定は常にステートメントの中および前に配置する必要があります。

declare
v_sal	number:=&sal;
v_job	varchar2(20):=&job;
begin
if (v_job='销售') and (check_sal(v_sal) > 5000)
then
dbms_output.put_line('符合匹配的and条件');
end if;
end;

暗黙的な型変換を避ける

おすすめ

転載: blog.csdn.net/qq_38696286/article/details/119213244