【PL/SQL】パフォーマンス改善プレイブック|4階層モデル・チェックリスト15・RESULT_CACHE・NOCOPY・NATIVE・計測駆動の体系

【PL/SQL】パフォーマンス改善のためのヒントと注意点 PL/SQL

PL/SQLのパフォーマンス改善は「とりあえずBULK COLLECTを使う」のような断片的なテクニック適用ではなく、計測→仮説→改善→確認のサイクルを体系的に回すことで初めて成果が出ます。何の計測もせずに「FORALLで速くなったはず」と思い込んでリリースした結果、別のレイヤがボトルネックで全く速くなっていなかった——というのは現場で頻発する失敗パターンです。

PL/SQLの性能には4つの階層があり、SQL層 → PL/SQL層 → コンパイル層 → メモリ層の順で改善効果が大きい構造になっています。いきなり末端の最適化(NATIVEコンパイル等)に手を出す前に、上位のSQL層・PL/SQL層を見直す方が桁違いの効果が出ます。この優先順位を意識せず手当たり次第にチューニングしても「数%しか速くならない」結果に終わりがちです。

この記事ではPL/SQLパフォーマンス改善の包括的プレイブックとして、計測ツールの使い分け、4階層別の主要テクニック、ボトルネック特定のフロー、効果が大きい順のチェックリスト、アンチパターン、ベンチ計測スニペット、FAQまで2026年版で整理します。深掘りが必要な技法は本サイト内の専門記事にリンクで誘導するため、本記事を「自プロジェクトの性能診断のロードマップ」として活用してください。

この記事でわかること

  • PL/SQLパフォーマンスの4階層(SQL/PL/SQL/コンパイル/メモリ)の捉え方
  • 効果の大きい順に並べた性能改善チェックリスト15項目
  • 計測ツール(DBMS_PROFILER/DBMS_HPROF/AUTOTRACE/TKPROF)の使い分け
  • ボトルネック特定のステップワイズフロー
  • NOCOPY/DETERMINISTIC/RESULT_CACHEの効果と適用条件
  • PLSQL_OPTIMIZE_LEVEL・NATIVEコンパイルの真の効果
  • PLS_INTEGER/BINARY_INTEGER最適化の数値型選択
  • 性能を逆に劣化させるアンチパターン7選
  • 改善前後を客観評価するベンチ計測スニペット
スポンサーリンク
  1. 30秒でわかるPL/SQL性能改善の結論
  2. 性能改善の4階層モデル|効果の大きい順に手を入れる
    1. 第1層|SQL層(効果10倍〜1000倍)
    2. 第2層|PL/SQL層(効果10倍〜100倍)
    3. 第3層|コンパイル層(効果1.2倍〜2倍)
    4. 第4層|メモリ層(効果1.05倍〜1.5倍)
  3. 計測ツールの使い分け|何を調べるかで道具を選ぶ
    1. DBMS_HPROF|階層プロファイラ(PL/SQL関数別の時間配分)
    2. AUTOTRACE / EXPLAIN PLAN|SQLの実行計画
    3. 10046トレース+TKPROF|SQL別の詳細時間
    4. V$SQLAREA / V$SQLSTATS|サマリ集計
  4. 効果の大きい順|性能改善チェックリスト15項目
  5. PL/SQL固有の最適化テクニック|知らないと損する5機能
    1. RESULT_CACHE|同入力同出力の関数を一発高速化
    2. NOCOPY|大きい引数のコピーオーバーヘッドを削減
    3. DETERMINISTIC|オプティマイザに「副作用なし」を伝える
    4. PLSQL_OPTIMIZE_LEVEL|コンパイラ最適化レベル
    5. NATIVEコンパイル|PL/SQLをCネイティブコードへ
  6. 改善前後をベンチ計測する|客観的に効果を評価
  7. 性能を逆に劣化させるアンチパターン7選
    1. ① 計測せずチューニング
    2. ② 何でもRESULT_CACHE
    3. ③ ループ内で関数呼び出し
    4. ④ 大量INSERTを1件ずつCOMMIT
    5. ⑤ NUMBERで全変数を宣言
    6. ⑥ 巨大コレクションをINで渡す
    7. ⑦ 既に最適化済みの関数を再度チューニング
  8. よくある質問
  9. 関連記事で深掘りする
  10. まとめ|計測駆動で性能改善を体系化する

30秒でわかるPL/SQL性能改善の結論

忙しい読者向けの結論先出しです。優先順位順に並べています。

結論 理由・効果
① まず計測してから改善する 勘で動かしたチューニングは別のボトルネックを動かして無意味
SQL層の最適化が圧倒的に効果大 10倍〜1000倍の差。索引・JOIN方式・実行計画の方が支配的
行ループ→集合演算に書き換える 1行1行ループするPL/SQLを1本のSQLにまとめる方が速い
④ どうしてもループするならBULK COLLECT+FORALL コンテキストスイッチを激減させる王道。10〜100倍の差
⑤ 関数呼び出しが多いならRESULT_CACHEを検討 同入力同出力の関数なら最大1000倍以上の差
⑥ 大きな引数はNOCOPYで参照渡し OUT・IN OUTのコピーオーバーヘッドを削減
⑦ ループカウンタはPLS_INTEGER NUMBER比1.5〜2倍速。整数演算なら必ず採用

性能改善の4階層モデル|効果の大きい順に手を入れる

PL/SQL性能には階層があり、上位の層ほど改善効果が大きいという法則があります。この優先順位を意識せず下層から手を付けると「数%しか速くならない」結果に終わるので、必ず上から順に確認してください。

第1層|SQL層(効果10倍〜1000倍)

圧倒的に支配的なのがこの層です。PL/SQL内のSQL文の実行計画・索引利用・JOIN方式を見直すだけで体感が桁違いに変わります。EXPLAIN PLANDBMS_XPLAN.DISPLAY_CURSORで現状を可視化し、INDEX RANGE SCANになるべき箇所がFULL TABLE SCANになっていないか、NESTED LOOPSになるべきがHASH JOINになっていないか(あるいは逆)を最初に確認します。

第2層|PL/SQL層(効果10倍〜100倍)

SQL自体は最適でも、PL/SQLからの呼び出し方が悪いとコンテキストスイッチで時間を失います。行ループ→集合演算化、BULK COLLECT+FORALL化、不要な関数呼び出しの削除、CASE分岐の最適化など。ボトルネック関数の特定にはDBMS_HPROF(階層プロファイラ)を使います。

第3層|コンパイル層(効果1.2倍〜2倍)

PL/SQLコンパイラの最適化レベル・NATIVEコンパイル・条件付きコンパイル。効果は比較的小さいですが「全体に効く」ので大量バッチでは累積効果が見えます。デフォルト設定で取りこぼしているケースが多いので新規プロジェクトでは必ず確認してください。

第4層|メモリ層(効果1.05倍〜1.5倍)

PLS_INTEGER・BINARY_INTEGER採用、SUBTYPE活用、パッケージステートでのキャッシュなど。効果は数%ですが大量ループでは累積で1.5倍にもなります。細部にこだわる前に、まず上の3層を確認するのが鉄則です。

性能チューニングの黄金則:①プロファイルを取る、②最も時間を食っている処理を特定、③その処理が属する層を判別、④その層の改善テクニックから順に試す、⑤改善後に再度プロファイルを取って効果検証。「ループは何でもFORALLにすれば速くなる」のような思い込みベースのチューニングは時間の無駄になりがちです。

計測ツールの使い分け|何を調べるかで道具を選ぶ

計測ツールはそれぞれ得意領域が異なります。「どのSQLが遅い」「どのPL/SQL関数が遅い」「実行計画はどうなっている」「実行回数は何回か」を調べるツールは別物なので、目的に合わせて選びます。

DBMS_HPROF|階層プロファイラ(PL/SQL関数別の時間配分)

「どの関数が何回呼ばれて合計何秒かかったか」を可視化するPL/SQL専用プロファイラ。関数のコールツリー構造で時間の累積を表示するため、「全体の80%の時間を食っている関数」を一発で特定できます。使い方はDBMS_PROFILER/DBMS_HPROFのプロファイラ完全ガイドを参照してください。

AUTOTRACE / EXPLAIN PLAN|SQLの実行計画

SQL層のボトルネック特定はこれ。SQL*Plus/SQLclでSET AUTOTRACE ONを実行すると実行計画と統計情報(consistent gets・physical reads)が同時に取れます。DBMS_XPLAN.DISPLAY_CURSORで実際の実行計画を見るのも実行時統計込みで強力です。

10046トレース+TKPROF|SQL別の詳細時間

ALTER SESSION SET EVENTS '10046 trace name context forever, level 12'でSQL実行ごとの詳細トレースを取得し、TKPROFで整形分析します。ロック待機・I/O待機など待機イベントレベルで原因を切り分けるときに使う本格ツールです。

V$SQLAREA / V$SQLSTATS|サマリ集計

「直近100時間で最も時間を食ったSQL TOP10」のような集計にはこれ。プロファイラを仕掛けるほどの調査をする前の予備診断に向きます。

計測ツールの実行サンプル
-- ① DBMS_HPROFで階層プロファイル
BEGIN
  DBMS_HPROF.START_PROFILING('PLSHPROF_DIR', 'profile_$(date).trc');
  -- 計測対象の処理を実行
  pkg_order_service.run_daily_batch;
  DBMS_HPROF.STOP_PROFILING;
END;
/
-- 出力ファイルを plshprof で整形
-- $ plshprof -output report profile.trc

-- ② SQL*PlusでAUTOTRACE
SET AUTOTRACE ON
SELECT * FROM orders WHERE customer_id = 100;
-- 実行計画とconsistent gets/physical readsが表示される

-- ③ 直近のヘビーSQLを特定
SELECT sql_id, executions, elapsed_time/1000000 AS sec_total,
       elapsed_time/executions/1000 AS msec_per_exec,
       SUBSTR(sql_text,1,80) AS sql_preview
  FROM v$sqlstats
 WHERE last_active_time > SYSDATE - 1
 ORDER BY elapsed_time DESC
 FETCH FIRST 10 ROWS ONLY;

-- ④ 実行計画の詳細(実際のバインド値・実行時統計)
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(
  sql_id => '&sql_id',
  format => 'ALLSTATS LAST +PEEKED_BINDS'
));

効果の大きい順|性能改善チェックリスト15項目

実務で効くチェックリストを「効果の大きい順」に並べました。上から順に確認していけば最短で改善効果が積み上がります。各項目の詳細実装は対応する深掘り記事を参照してください。

順位 確認項目 解説/参考記事
1 SQLの実行計画を確認 FULL SCAN・遅いJOIN・統計情報の古さを最優先で診断
2 索引が適切に使われているか WHERE列・ORDER BY列・JOIN列に索引があるか/関数索引で代替不要か
3 1行ループを集合SQLへ書き換え可能か SELECTで取得→ループ更新を1本のUPDATEまたはMERGE文へ
4 BULK COLLECT+FORALL適用 BULK完全ガイド/LIMIT 100〜1000で10〜100倍高速化
5 UPSERTはMERGE文化 MERGE完全ガイド/IF方式比5〜8倍速
6 関数のRESULT_CACHE化 同入力同出力の関数で最大1000倍。RESULT_CACHE完全ガイド
7 大きな引数にNOCOPY VARCHAR2(32767)・コレクション型のOUT/IN OUTにNOCOPY指定
8 カーソル選択の見直し FORループ vs 明示カーソル/10g以降の暗黙BULK 100件最適化
9 動的SQLのバインド変数化 動的SQLセキュア化/カーソル共有でパース削減
10 大量データはパイプライン関数 パイプライン関数完全ガイド/PGA消費を一定化
11 PLSQL_OPTIMIZE_LEVEL確認 3が最高(11g以降のデフォルト2でもOK)。1や0にしているなら即修正
12 NATIVEコンパイル 計算集中型の関数で1.2〜2倍。INTERPRETEDよりCPU効率向上
13 PLS_INTEGER採用 ループカウンタ・整数演算でNUMBER比1.5〜2倍速
14 パッケージステートでキャッシュ セッション内で繰り返し参照する設定値はパッケージ変数で保持
15 大量バッチは並列化検討 DBMS_PARALLEL_EXECUTE・PARALLEL_ENABLEで複数CPU活用

PL/SQL固有の最適化テクニック|知らないと損する5機能

SQL層の最適化は別記事に譲り、ここでは「PL/SQLレベルでの設定で大きく差がつく」5機能を整理します。いずれも適用条件を理解した上で使うのが重要です。

RESULT_CACHE|同入力同出力の関数を一発高速化

関数の戻り値をライブラリキャッシュに保存し、同じ入力なら関数本体を実行せずキャッシュを返します。対象テーブルが変更されたら自動で無効化されるため正確性も担保されます。副作用がない・呼び出し頻度が高い・テーブル変更頻度が低い、の3条件が揃う関数で最大1000倍以上の高速化が可能。RETURN ... DETERMINISTIC RESULT_CACHEを関数定義に追加するだけで有効になります。

NOCOPY|大きい引数のコピーオーバーヘッドを削減

OUT・IN OUTパラメータはデフォルトで「コピーして渡す」値渡しのため、VARCHAR2(32767)コレクション型を渡すとメモリコピーが発生します。NOCOPYヒントを付けると参照渡しに切り替わり、メモリコピーが消えます。巨大なコレクション型のIN OUTでは体感できる差が出ます。

DETERMINISTIC|オプティマイザに「副作用なし」を伝える

同じ入力なら必ず同じ出力を返す関数にDETERMINISTICを付与すると、オプティマイザがSQL内呼び出しを省略できる場面で省略します。関数索引・マテリアライズドビューでも必須の宣言で、RESULT_CACHEと組み合わせるのが定番です。

PLSQL_OPTIMIZE_LEVEL|コンパイラ最適化レベル

パッケージ単位で設定可能なオプティマイザレベル。0(最適化なし)/1(最低)/2(デフォルト・推奨)/3(最高・自動インライン化等)。12c以降はデフォルト2でも十分高速ですが、計算重視のパッケージは3を試す価値があります。INFORMATIONALな副作用警告が増えるため、コンパイル警告管理と併用してください。

NATIVEコンパイル|PL/SQLをCネイティブコードへ

ALTER SESSION SET PLSQL_CODE_TYPE=NATIVEで有効化。計算集中型処理(数値演算・文字列加工)で1.2〜2倍の高速化が期待できます。SQL中心のコードでは効果は小さいので、純粋にPL/SQL演算が支配的な関数だけに適用するのが現実解です。詳細はPL/SQLネイティブコンパイル完全ガイドを参照してください。

5機能を組み合わせた最適化サンプル
-- ✅ DETERMINISTIC + RESULT_CACHE で最大1000倍速
CREATE OR REPLACE FUNCTION pkg_tax.calc(p_country VARCHAR2)
  RETURN NUMBER
  DETERMINISTIC
  RESULT_CACHE RELIES_ON (countries)
AS
  v_rate NUMBER;
BEGIN
  SELECT rate INTO v_rate FROM countries WHERE code = p_country;
  RETURN v_rate;
END;
/

-- ✅ NOCOPY で巨大コレクションのコピー削減
CREATE OR REPLACE PROCEDURE pkg_bulk.process(
  p_data IN OUT NOCOPY t_huge_collection
) AS
BEGIN
  -- 100MB級のコレクションでもコピー発生せず即座に処理開始
  FOR i IN 1 .. p_data.COUNT LOOP
    p_data(i).status := 'PROCESSED';
  END LOOP;
END;
/

-- ✅ パッケージ単位で最適化レベル3を恒久設定
ALTER PACKAGE pkg_calc COMPILE PLSQL_OPTIMIZE_LEVEL=3 REUSE SETTINGS;

-- ✅ NATIVEコンパイル(計算集中パッケージ)
ALTER PACKAGE pkg_calc COMPILE PLSQL_CODE_TYPE=NATIVE REUSE SETTINGS;

-- ✅ 設定の現状確認
SELECT name, type, plsql_optimize_level, plsql_code_type
  FROM user_plsql_object_settings
 WHERE name = 'PKG_CALC';

RESULT_CACHEの落とし穴:①関数内に副作用(INSERT/UPDATE等)があると正常動作しない、②RELIES_ONで参照テーブルを宣言しないと無効化されない可能性、③ライブラリキャッシュの容量を消費するため使いすぎると逆効果。「副作用ゼロ・参照テーブルが少ない・呼び出し頻度が極端に高い」の3条件が揃う関数だけに適用してください。

改善前後をベンチ計測する|客観的に効果を評価

「速くなった気がする」では本当に効いたか分からないので、同条件で改善前後を計測するベンチスニペットを常備しておくのがプロの流儀です。次のコードはコピペで使える計測テンプレートです。

改善前後のベンチ計測テンプレート
-- 計測ヘルパープロシージャ
CREATE OR REPLACE PROCEDURE bench(
  p_label IN VARCHAR2,
  p_iters IN NUMBER DEFAULT 1
) AS
  v_t0  TIMESTAMP;
  v_t1  TIMESTAMP;
BEGIN
  v_t0 := SYSTIMESTAMP;
  -- ここに計測対象を呼び出すコードを書く(例)
  FOR i IN 1 .. p_iters LOOP
    NULL;  -- 実装に置き換える
  END LOOP;
  v_t1 := SYSTIMESTAMP;
  DBMS_OUTPUT.PUT_LINE(
    p_label || ' / iters=' || p_iters ||
    ' / elapsed=' || EXTRACT(SECOND FROM (v_t1-v_t0)) || 's');
END;
/

-- 改善前(行ループ実装)
DECLARE
  v_t0 TIMESTAMP := SYSTIMESTAMP;
BEGIN
  FOR rec IN (SELECT id FROM input_data) LOOP
    UPDATE target_table SET status = 'X' WHERE id = rec.id;
  END LOOP;
  COMMIT;
  DBMS_OUTPUT.PUT_LINE(
    'BEFORE: ' ||
    EXTRACT(SECOND FROM (SYSTIMESTAMP - v_t0)) || 's');
END;
/

-- 改善後(集合UPDATE)
DECLARE
  v_t0 TIMESTAMP := SYSTIMESTAMP;
BEGIN
  UPDATE target_table SET status = 'X'
   WHERE id IN (SELECT id FROM input_data);
  COMMIT;
  DBMS_OUTPUT.PUT_LINE(
    'AFTER:  ' ||
    EXTRACT(SECOND FROM (SYSTIMESTAMP - v_t0)) || 's');
END;
/

-- 統計情報も併せて取得(CPU時間・物理I/O)
DECLARE
  v_t0   TIMESTAMP := SYSTIMESTAMP;
  v_cpu0 NUMBER; v_cpu1 NUMBER;
  v_io0  NUMBER; v_io1  NUMBER;
BEGIN
  SELECT value INTO v_cpu0 FROM v$mystat ms JOIN v$statname sn USING(statistic#)
   WHERE sn.name = 'CPU used by this session';
  SELECT value INTO v_io0  FROM v$mystat ms JOIN v$statname sn USING(statistic#)
   WHERE sn.name = 'physical reads';

  -- 計測対象を実行
  pkg_order_service.run_daily_batch;

  SELECT value INTO v_cpu1 FROM v$mystat ms JOIN v$statname sn USING(statistic#)
   WHERE sn.name = 'CPU used by this session';
  SELECT value INTO v_io1  FROM v$mystat ms JOIN v$statname sn USING(statistic#)
   WHERE sn.name = 'physical reads';

  DBMS_OUTPUT.PUT_LINE('Elapsed:    ' ||
    EXTRACT(SECOND FROM (SYSTIMESTAMP - v_t0)) || 's');
  DBMS_OUTPUT.PUT_LINE('CPU used:   ' || (v_cpu1 - v_cpu0) || ' centisec');
  DBMS_OUTPUT.PUT_LINE('Phys reads: ' || (v_io1  - v_io0));
END;
/

性能を逆に劣化させるアンチパターン7選

① 計測せずチューニング

勘でループをFORALLにしたら別の処理がボトルネックで全く速くならなかった、というのは典型例。必ずプロファイルで「最も時間を食っている処理」を特定してから改善対象を決めてください。

② 何でもRESULT_CACHE

呼び出し頻度の低い関数や、参照テーブルが頻繁に更新される関数にRESULT_CACHEを付けるとライブラリキャッシュを汚染して逆効果。「副作用ゼロ・参照テーブル変動小・呼び出し頻度極大」の3条件を満たす関数だけに絞ってください。

③ ループ内で関数呼び出し

SELECT結果に対する単純な変換でも、PL/SQL関数を1行ごとに呼ぶとコンテキストスイッチで激遅になります。関数をWITH FUNCTION句でインライン化、またはCASE式やSQL関数で表現できないか先に検討してください。

④ 大量INSERTを1件ずつCOMMIT

10万件INSERTで毎件COMMITすると、1コミットごとのディスク書き込みでI/Oが律速します。バッチ処理は最後にまとめて1回COMMIT、またはバッチサイズ1000〜10000ごとにコミットするのが定石です。コミット頻度とUNDO最適化を参照。

⑤ NUMBERで全変数を宣言

整数演算(特にループカウンタ)にNUMBERを使うとPLS_INTEGER比1.5〜2倍遅くなります。小数や大整数が不要なら必ずPLS_INTEGERを選んでください。

⑥ 巨大コレクションをINで渡す

引数モードはデフォルトIN(コピー渡し)なので、100万件のコレクション型を渡すとメモリコピーで遅延します。IN OUT+NOCOPYで参照渡しに切り替えてください。INのままなら呼び出し側でグローバル変数に置く設計も検討。

⑦ 既に最適化済みの関数を再度チューニング

「もっと速くなるはず」と既に十分速い関数をいじるのは時間の無駄。全体実行時間の80%を占めていない処理は最適化対象から外すのがアムダールの法則。プロファイルで上位を抜き出し、上から順に改善するのが鉄則です。

よくある質問

QPL/SQL性能改善で最初に何を見るべきですか?
ASQLの実行計画です。PL/SQL内部の最適化を頑張る前に、まずはEXPLAIN PLANまたはDBMS_XPLAN.DISPLAY_CURSORでSQLが索引を使っているか・JOIN方式が適切かを確認してください。統計情報が古いだけで桁違いに遅くなっているケースが現場では非常に多く、DBMS_STATS.GATHER_TABLE_STATSを回すだけで10倍速くなることも珍しくありません。
QBULK COLLECTのLIMIT値は何が最適ですか?
A一般的には100〜1000が実用範囲。小さすぎるとコンテキストスイッチが残り、大きすぎるとPGAを圧迫します。実装時はLIMIT 1000から始め、メモリ使用量と速度を実測しながら調整してください。詳細はバルク処理完全ガイドを参照してください。
QNATIVEコンパイルは本番でも安全ですか?
A安全です。Oracle公式の最適化機構で、ALTER PACKAGE X COMPILE PLSQL_CODE_TYPE=NATIVE REUSE SETTINGSでパッケージ単位で切り替えできます。ただしSQL中心のコードでは効果が小さいので、計算集中型のパッケージだけに絞るのが現実解です。NATIVEに切り替えても動作仕様は完全にINTERPRETEDと同じなのでバグ混入の心配はありません。
QRESULT_CACHEとパッケージ変数キャッシュ、どちらを使うべき?
A用途で使い分けます。RESULT_CACHEは全セッション共通のライブラリキャッシュで基底テーブル変更時に自動無効化されるので正確性が担保されます。パッケージ変数キャッシュはセッションローカルなので高速ですが、明示的なリフレッシュ機構が必要です。「全体で共有・自動無効化したい」ならRESULT_CACHE、「セッション固有・極小オーバーヘッドで使いたい」ならパッケージ変数、です。
QPLS_INTEGERとBINARY_INTEGERの違いは?
A実体は同じ32ビット整数で性能差はありません。PLS_INTEGERの方が新しい標準的な書き方なので、新規コードはPLS_INTEGERを推奨します。念のため大数値が必要な場合はSIMPLE_INTEGER(オーバーフロー検査なし・最速)を選ぶ手もあります。いずれもNUMBER比で1.5〜2倍速いので、整数演算なら必ずこのファミリーを選んでください。
Q本番環境でDBMS_HPROFを実行しても大丈夫?
A計測オーバーヘッドは数〜10%程度なので本番でも実行可能です(深夜バッチで一時的に有効化等)。長時間放置するとトレースファイルがディスクを圧迫するのでスポット計測が原則。ピーク時間帯は避け、計測後は確実にSTOP_PROFILINGを呼んでください。
Q統計情報はどのくらいの頻度で更新すべき?
Aデフォルトの夜間自動収集SCHEDULER_SCHEMA_STATS_JOB)に任せるのが基本。日中に大量データロードがあった場合は手動でDBMS_STATS.GATHER_TABLE_STATSを回してください。パーティション表はGRANULARITY=>'AUTO'で増分収集するとロードコストを抑えられます。
Q並列化(PARALLEL)はどんなときに使う?
ACPU律速の大量バッチで1セッションでは時間内に終わらないときに使います。小規模データ・OLTP・短時間処理では並列化のオーバーヘッドが上回り逆に遅くなることもあります。判断基準は「処理時間が10分以上」「データ量が数千万件以上」「CPUコアに余裕がある」の3つを満たす場合です。PL/SQLではDBMS_PARALLEL_EXECUTEで実装しやすく、失敗時の再実行ロジックも組み込みやすい仕組みになっています。
Q結果が同じでも複数の書き方がある場合、どれが速い?
A迷ったらシンプル+集合演算を選んでください。一般則は「①SQL集合演算 > ②BULK COLLECT+FORALL > ③カーソルFORループ > ④明示的カーソル」。集合演算で書ければ最速で、PL/SQLループに落とすほど遅くなります。可読性も大事なので過度な短縮は避けつつ、「ロジックが集合操作で表現できるなら集合操作を選ぶ」という判断基準を持ってください。
Q既存システムの性能改善はどこから手を付ける?
A次の順序で進めるのが現実的です。①V$SQLSTATSで「直近で最も時間を食ったSQL TOP10」を抽出、②その中の上位3本に絞ってEXPLAIN PLAN/DBMS_HPROFで詳細診断、③索引追加・SQL書き換え・統計情報更新で最大の効果を狙う、④それでも遅い処理についてPL/SQL層の改善(BULK COLLECT等)を検討、⑤最後に細部のNOCOPY・PLS_INTEGER・NATIVE等を適用。「上位3SQLを倒すだけで全体の70%は改善する」のがアムダールの法則的な経験則です。

関連記事で深掘りする

本記事はPL/SQLパフォーマンス改善の全体像を示すハブ記事です。個別テクニックの詳細は以下の専門記事で深掘りしてください。

まとめ|計測駆動で性能改善を体系化する

PL/SQLパフォーマンス改善は「計測→仮説→改善→確認」のサイクル4階層の優先順位(SQL→PL/SQL→コンパイル→メモリ)を理解して進めれば、確実に成果が出ます。本記事の要点を7つに集約します。

  1. 勘でチューニングせず必ずDBMS_HPROFやV$SQLSTATSで計測してから始める
  2. SQL層の改善(索引・実行計画・統計情報)が最大効果。最初に必ず確認
  3. 1行ループは集合SQLに、それが無理ならBULK COLLECT+FORALLで10〜100倍速
  4. UPSERTはMERGE、関数はRESULT_CACHE、大引数はNOCOPYで簡単高速化
  5. PLSQL_OPTIMIZE_LEVEL=2以上、必要に応じてNATIVEコンパイルも検討
  6. 整数演算は必ずPLS_INTEGER/SIMPLE_INTEGERでNUMBER比1.5〜2倍速
  7. 改善前後のベンチ計測を必ず取り、客観評価でリリース可否を判定

性能改善は「銀の弾丸」ではなく地道な計測と検証の積み重ねです。「上位3SQLを倒すだけで全体の70%は改善する」というアムダールの法則の通り、時間を食っている処理を狙い撃ちで改善するのが最短ルート。本記事のチェックリスト15項目を自プロジェクトに適用し、深掘りが必要な技法は対応する専門記事で実装テクニックを確認してみてください。