PL/SQLの変更をデプロイすると、依存関係の連鎖によってオブジェクトがINVALID化し、次の実行時に再コンパイルが走ることがあります。無秩序なINVALIDは初回実行の遅延や予期せぬ失敗を招くため、依存の把握と再コンパイルの制御を設計に組み込むことが重要です。ここでは依存の可視化、INVALIDの原因、正しいコンパイル順序、UTL_RECOMPやDBMS_UTILITYを使った一括再コンパイル、EBRや細粒度依存関係での影響最小化までを実務の型としてまとめます。
依存関係を把握して影響範囲を特定する
仕様(パッケージSpec)に触れると広くINVALIDが波及し、Bodyだけなら依存側は基本的に影響を受けません。作業前に依存グラフを確認し、どこまで影響が及ぶかを具体的に見積もっておくと安全です。
-- 指定オブジェクトに依存する下流の一覧(呼び出し元を知る)
SELECT name, type, referenced_name, referenced_type
FROM user_dependencies
WHERE referenced_name = UPPER(:obj_name)
ORDER BY type, name;
-- 現在スキーマのINVALID一覧を俯瞰
SELECT object_name, object_type
FROM user_objects
WHERE status = 'INVALID'
ORDER BY object_type, object_name;
依存の深い環境では、まず仕様変更を最小化し、どうしても変える場合は一度に関連オブジェクトを再コンパイルして初回実行での遅延を防ぐのが定石です。
INVALIDが発生する典型要因を理解する
テーブルやビューへのDDL、パッケージ仕様の変更、シノニムの付け替え、権限の剥奪や付与、依存先の再作成などがトリガになります。Oracle 11g以降は細粒度依存関係(Fine-Grained Dependency)で列レベルの追跡が行われるため、無関係な変更で広範にINVALID化されるケースは減りましたが、仕様のシグネチャが変わると下流は依然としてINVALIDになります。運用ではDDLウィンドウを設け、再コンパイルと統計更新までを一連の作業として扱うと安定します。
正しいコンパイル順序の基本方針
順序は概ね「型やオブジェクトタイプ定義」→「パッケージ仕様」→「パッケージ本体」→「関数・プロシージャ」→「ビュー」→「マテリアライズド・ビュー」→「シノニム・権限確認」の流れを守ると収束が早まります。パッケージはSPECとBODYを個別にコンパイルでき、仕様が通ればBODYが再解釈されるため、仕様の安定化が最重要です。
-- 単体での再コンパイル例
ALTER PACKAGE app_api COMPILE SPECIFICATION;
ALTER PACKAGE app_api COMPILE BODY;
ALTER PROCEDURE p_calc COMPILE;
ALTER FUNCTION f_conv COMPILE;
ALTER VIEW v_sales COMPILE;
一括再コンパイルの自動化(UTL_RECOMP と DBMS_UTILITY)
INVALIDが多い場合は手作業ではなくパッケージを使って機械的に収束させます。UTL_RECOMPは依存順序を解決しながら並列または直列で再コンパイルを実行します。大量のINVALIDを短時間で片付けたいときは並列版、負荷を抑えたい環境では直列版が有効です。
-- スキーマ単位で直列リコンパイル(負荷低)
BEGIN
UTL_RECOMP.RECOMP_SERIAL(schema => USER);
END;
/
-- データベース全体を並列リコンパイル(DBA権限・メンテナンス時間帯向け)
BEGIN
UTL_RECOMP.RECOMP_PARALLEL(
threads => 8 -- CPUと並列度に応じて調整
);
END;
/
DBMS_UTILITY.COMPILE_SCHEMAは旧来の手段ですが、単純なスキーマ内再コンパイルには手軽です。
BEGIN
DBMS_UTILITY.COMPILE_SCHEMA(schema => USER, compile_all => FALSE);
END;
/
実行後はINVALIDが残っていないか再確認し、残ったものは依存切れや権限不足、存在しないシノニムなど個別要因を疑います。
デプロイ時の安全な手順とウォームアップ
リリースの基本は「仕様の変更は最小限」「BODYの差し替えは自由」「最後に一括再コンパイル」「代表クエリでウォームアップ」の四点です。初回アクセス時の自動再コンパイルを避けるため、デプロイ直後に主要パッケージやビューを1回ずつ呼ぶ軽量スモークテストを流し、実行計画のキャッシュとPL/SQLのネイティブコード生成(設定により)を温めておくと初回遅延を防げます。
-- スモークテスト例(例外は拾ってログ)
BEGIN
DECLARE dummy NUMBER; BEGIN dummy := app_api.health_check(); END;
BEGIN NULL; END; -- 他の主要ユニットも軽く起動
END;
/
INVALID連鎖の根治策:仕様安定化とインターフェース分離
仕様が頻繁に変わるパッケージは、呼び出し側まで連鎖INVALIDの震源になります。公開インターフェースは薄いファサード仕様に固定し、その背後で実体パッケージや内部型を入れ替える二層構成にすると、BODY内の変更で完結しやすくなります。ビューも同様に、下層テーブル変更の影響を受けにくい安定スキーマ(別名・生成列)を用意することで、上位の依存を守れます。
Edition-Based Redefinition(EBR)で無停止切替を実現する
長時間のINVALIDを避け、無停止で仕様差分を導入したい場合はエディションベース・リディフィニションの選択肢があります。新エディションで変更を加え、コンパイルと検証を済ませた後、セッションのデフォルトエディションを切り替えることで、既存セッションを維持しながら新ロジックへ段階移行できます。シノニムやビューはエディション化オブジェクトに寄せ、非エディション化テーブルはEditioning Viewを介してスキーマ進化を吸収する設計が有効です。
原因切り分けのポイントと代表的な詰まりどころ
再コンパイルしてもINVALIDが残る場合は、参照先の権限不足、存在しないシノニムの参照、外部データベースリンクの不通、COMPILE時点で解決不能な表・列の欠落が典型です。依存ビューが壊れているのに上位ビューだけをCOMPILEしても解決しないため、下流から順に収束させる発想が欠かせません。DBA_DEPENDENCIESで根のオブジェクトを探し、まずそれを直すと連鎖的に治ることが多くあります。
運用テンプレート:デプロイ後の自動収束スクリプト
毎回の手戻りをなくすため、デプロイ直後に「INVALID収束→統計更新→健全性チェック」を自動で回すテンプレートを用意しておきます。以下はスキーマ限定での収束例です。
DECLARE
v_before NUMBER;
v_after NUMBER;
BEGIN
SELECT COUNT(*) INTO v_before FROM user_objects WHERE status='INVALID';
UTL_RECOMP.RECOMP_SERIAL(schema => USER);
SELECT COUNT(*) INTO v_after FROM user_objects WHERE status='INVALID';
IF v_after > 0 THEN
-- 依存切れ候補を出力(要件に応じてログ表へ記録)
FOR r IN (SELECT object_name, object_type FROM user_objects WHERE status='INVALID') LOOP
DBMS_OUTPUT.PUT_LINE('STILL INVALID: '||r.object_type||' '||r.object_name);
END LOOP;
RAISE_APPLICATION_ERROR(-20001, 'invalid remains='||v_after||' (before='||v_before||')');
END IF;
END;
/
この直後に必要範囲のDBMS_STATS.GATHER_*で統計を整え、代表ジョブやAPIを軽く叩いてウォームアップする流れまでを一つのジョブとして固めておくと、初回遅延や予期せぬ失敗を大きく減らせます。
まとめ
依存オブジェクトの管理とINVALID再コンパイルの制御は、仕様・本体の分離設計、影響範囲の事前把握、正しいコンパイル順序の遵守、一括再コンパイルの自動化、そしてデプロイ後のウォームアップまでをセットで運用するのが肝要です。細粒度依存とEBRを味方につけて影響を最小化し、仕様は安定、実装は俊敏という原則に沿って設計すれば、リリースたびに発生するINVALIDの混乱から解放され、予測可能で安定したPL/SQL基盤を維持できます。