【Oracle】ORA-04068 完全ガイド|パッケージ状態が破棄されるエラーの原因・対処・PRAGMA SERIALLY_REUSABLE まで解説

Oracle で稼働中のシステムのパッケージを再コンパイルした直後に、既存セッションから呼び出すと ORA-04068: existing state of packages has been discarded(パッケージの既存の状態は廃棄されました)が発生することがあります。

このエラーはパッケージに変数やカーソルなどの「状態(State)」を持つ場合に発生します。パッケージを再コンパイルすると既存セッションのパッケージ状態が無効化され、次の呼び出し時に Oracle がエラーを通知します。同じ呼び出しを 2 回目に実行すると通常は正常に動作するのが特徴で、この挙動を知っていると原因を素早く特定できます。

この記事でわかること

  • ORA-04068 が発生する仕組みと「2 回目は成功する」理由
  • パッケージに「状態」がある場合と状態がない場合の違い
  • アプリケーション側でのリトライ処理(ORA-04068 を検知して再実行)
  • PRAGMA SERIALLY_REUSABLE でパッケージ変数をコール間でリセットする方法
  • コネクションプール環境での ORA-04068 の影響範囲
  • パッケージ再コンパイル時の影響を最小化するベストプラクティス
スポンサーリンク

ORA-04068 が発生する仕組み

Oracle のパッケージがパッケージ変数(グローバル変数)やカーソル変数を持つ場合、その変数の値はセッションごとの UGA(User Global Area)に保持されます。これを「パッケージ状態(Package State)」と呼びます。

パッケージを再コンパイル(ALTER PACKAGE ... COMPILE など)すると、そのパッケージのオブジェクト番号が変わり、既存セッションが保持しているパッケージ状態が無効になります。次にそのパッケージを呼び出したとき、Oracle は無効になった状態を検知して ORA-04068 を発生させます。この時点でパッケージ状態は自動的にリセットされます。2 回目の呼び出しでは、新しくパッケージ状態が初期化されるため正常に動作します。

状態 動作
セッション開始後、初回呼び出し パッケージ状態が UGA に初期化される
パッケージ再コンパイル(別ユーザーが実行) 既存セッションの状態が無効化(Invalidated)される
再コンパイル後の最初の呼び出し(1 回目) ORA-04068 が発生し、パッケージ状態がリセットされる
同じ呼び出しの 2 回目 正常に動作する(状態が再初期化済み)
ORA-04068 を再現するシナリオ
-- パッケージ定義(グローバル変数を持つ)
CREATE OR REPLACE PACKAGE my_pkg AS
    g_count NUMBER := 0;    -- パッケージ変数(セッション単位で保持)
    PROCEDURE increment;
    FUNCTION get_count RETURN NUMBER;
END my_pkg;
/

CREATE OR REPLACE PACKAGE BODY my_pkg AS
    PROCEDURE increment IS
    BEGIN
        g_count := g_count + 1;
    END increment;

    FUNCTION get_count RETURN NUMBER IS
    BEGIN
        RETURN g_count;
    END get_count;
END my_pkg;
/

-- セッション A で使用中
BEGIN
    my_pkg.increment;
    my_pkg.increment;
    DBMS_OUTPUT.PUT_LINE(my_pkg.get_count);  -- → 2
END;
/

-- 別の DBA セッションがパッケージを再コンパイルする
ALTER PACKAGE my_pkg COMPILE BODY;

-- セッション A で再度呼び出す
BEGIN
    my_pkg.increment;  -- ← ここで ORA-04068 が発生する
END;
/
-- ORA-04068: existing state of packages ("HR"."MY_PKG") has been discarded
-- ORA-06512: at "HR.MY_PKG", line 4

-- もう一度呼び出すと成功する(状態がリセット済みのため)
BEGIN
    my_pkg.increment;  -- 成功(g_count は 0 にリセットされた後 1 になる)
END;
/

ORA-04068 への対処法

アプリケーション側でリトライ処理を実装する
-- ORA-04068 は再試行で回復するため、例外ハンドラでリトライする
CREATE OR REPLACE PROCEDURE safe_call_with_retry AS
    v_retry_count NUMBER := 0;
    e_pkg_state_discarded EXCEPTION;
    PRAGMA EXCEPTION_INIT(e_pkg_state_discarded, -4068);  -- ORA-04068 の宣言
BEGIN
    LOOP
        BEGIN
            -- パッケージを呼び出す処理
            my_pkg.increment;
            EXIT;  -- 成功したらループを抜ける

        EXCEPTION
            WHEN e_pkg_state_discarded THEN
                v_retry_count := v_retry_count + 1;
                IF v_retry_count >= 3 THEN
                    RAISE;  -- 3回リトライしても失敗したら再送出
                END IF;
                -- 次のループで再試行する(2回目は通常成功する)
        END;
    END LOOP;
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('エラー: ' || SQLERRM);
        RAISE;
END safe_call_with_retry;
/

-- Oracle の定義済み例外コード
-- ORA-04068: -4068 (existing state of packages has been discarded)
-- ORA-04061: -4061 (existing state of stored procedure has been invalidated)
-- ORA-04065: -4065 (not executed, altered or dropped ... procedure)
-- これらはいずれも再コンパイルや無効化に関連するエラー

PRAGMA SERIALLY_REUSABLE で状態のないパッケージにする

PRAGMA SERIALLY_REUSABLE を指定すると、パッケージ変数はサブプログラムの呼び出し終了時にリセットされます。セッション間で状態を共有せず、毎回初期値から始まります。この設定を使うと、パッケージは「状態を持たない」ため ORA-04068 が発生しにくくなります。

PRAGMA SERIALLY_REUSABLE でパッケージを状態なしにする
-- SERIALLY_REUSABLE: パッケージ変数を各コールの後にリセットする
CREATE OR REPLACE PACKAGE stateless_pkg AS
    PRAGMA SERIALLY_REUSABLE;    -- ← 仕様部と本体の両方に必要

    g_temp_count NUMBER := 0;   -- 呼び出し終了時に 0 にリセットされる
    PROCEDURE process_batch(p_count NUMBER);
END stateless_pkg;
/

CREATE OR REPLACE PACKAGE BODY stateless_pkg AS
    PRAGMA SERIALLY_REUSABLE;    -- ← 本体にも必要

    PROCEDURE process_batch(p_count NUMBER) IS
    BEGIN
        g_temp_count := p_count;
        -- 処理...
        DBMS_OUTPUT.PUT_LINE('処理数: ' || g_temp_count);
        -- この PROCEDURE が終了すると g_temp_count は 0 に戻る
    END process_batch;
END stateless_pkg;
/

-- SERIALLY_REUSABLE パッケージは:
-- ・各コールの後に変数がリセットされるため、セッション間で状態を持たない
-- ・再コンパイルしても既存セッションに影響を与えにくい
-- ・パッケージ変数をセッション間のデータ保持に使えない

-- 注意: SERIALLY_REUSABLE パッケージのカーソルはコール間で持続しない
-- OPEN したカーソルはそのコール内で FETCH して CLOSE する必要がある

コネクションプール環境での ORA-04068 の影響

コネクションプールと ORA-04068
コネクションプール(JDBC Connection Pool・UCP・Node.js connection pool など)では、データベース接続が複数のアプリケーションリクエスト間で使い回されます。パッケージが再コンパイルされると、プール内の各接続が最初の呼び出しで ORA-04068 を返す可能性があります。プール内の接続数だけ ORA-04068 が発生しますが、それぞれの 2 回目以降は正常に動作します。アプリケーションが ORA-04068 をハンドルしてリトライする実装になっているかを確認してください。
パッケージ状態を持つオブジェクトを確認する
-- パッケージ状態を持つセッションを確認する(DBA 権限が必要)
-- V$DB_OBJECT_CACHE: PL/SQL オブジェクトのキャッシュ情報
SELECT name, namespace, type, locks, pins
FROM V$DB_OBJECT_CACHE
WHERE type IN ('PACKAGE', 'PACKAGE BODY')
  AND locks > 0;
-- locks > 0: このパッケージを参照しているセッションが存在する

-- 無効なパッケージを確認する(再コンパイルが必要なオブジェクト)
SELECT object_name, object_type, status, last_ddl_time
FROM DBA_OBJECTS
WHERE owner = 'HR'
  AND status = 'INVALID'
  AND object_type IN ('PACKAGE', 'PACKAGE BODY')
ORDER BY last_ddl_time DESC;

-- メンテナンス時のパッケージ再コンパイル手順(影響を最小化)
-- 1. ピーク時間外に実行する(セッション数が少ない時間帯)
-- 2. デプロイ後にアプリケーションのウォームアップリクエストを送って事前に状態をリセットする
-- 3. ロードバランサーで一部のサーバーを切り離してからデプロイする(ローリングデプロイ)

まとめ

  • ORA-04068 の原因:パッケージ変数(状態)を持つパッケージを再コンパイルすると、既存セッションのパッケージ状態が無効化される
  • 2 回目で成功する理由:ORA-04068 発生時にパッケージ状態が自動リセットされ、次の呼び出しは正常に動作する
  • リトライ処理:PRAGMA EXCEPTION_INIT(-4068) で ORA-04068 を捕捉して再試行する。2 回目で通常は成功する
  • PRAGMA SERIALLY_REUSABLE:各コール後に変数をリセットする。状態を持たないパッケージになるため ORA-04068 が発生しにくい。ただしセッション間のデータ保持はできない
  • コネクションプール:プール内の各接続が1回ずつ ORA-04068 を発生させる。アプリケーション側のリトライ処理が重要
  • デプロイ戦略:ローリングデプロイ・ピーク時間外・ウォームアップリクエストで影響を最小化する

Oracle パッケージの基本構造と変数スコープについては Oracle パッケージ完全ガイドを参照してください。PRAGMA AUTONOMOUS_TRANSACTION を使ったエラーログ記録パターンは AUTONOMOUS_TRANSACTION 完全ガイドも参照してください。