【Oracle】グローバル一時表(GTT)完全ガイド|ON COMMIT DELETE/PRESERVE ROWS・セッション分離・インデックス・統計・WITH句との使い分け・実務パターン

【Oracle】グローバル一時表(GTT)完全ガイド|ON COMMIT DELETE/PRESERVE ROWS・セッション分離・インデックス・統計・WITH句との使い分け・実務パターン Oracle

グローバル一時表(GTT: Global Temporary Table)は、テーブル定義は全セッションで共有しながら、格納されたデータは各セッションにのみ見えるというOracle 独自のオブジェクトです。バッチ処理の中間データ・複雑な集計の一時保持・セッションをまたがない作業領域として広く使われます。

この記事でわかること

  • GTT の2種類:ON COMMIT DELETE ROWS(トランザクション)と ON COMMIT PRESERVE ROWS(セッション)
  • セッション間でデータが見えない仕組みと実務上の注意点
  • GTT のインデックス作成とセッションローカルな統計情報の設定
  • 通常テーブルより undo/redo 生成が少ないパフォーマンス特性
  • WITH句・サブクエリ・ワークテーブルとの使い分け
  • DDL(TRUNCATE・DROP)の挙動
  • バッチ処理・ETL でよく使う実務パターン
スポンサーリンク

グローバル一時表(GTT)とは

GTT は CREATE GLOBAL TEMPORARY TABLE で作成する特殊なテーブルで、以下の特性を持ちます。

  • 定義は全セッションで共有:テーブル定義は永続的に保存される(DROP するまで消えない)
  • データはセッション(またはトランザクション)ごとに分離:自分のセッションが INSERT したデータは他セッションから見えない
  • 自動削除:セッション終了時(または COMMIT 時)に自動でデータが削除される
  • undo/redo 生成が少ない:一時セグメントに書き込むため、通常テーブルよりロールバック情報が少ない

「グローバル」という名前はテーブル定義がグローバル(全員共有)という意味であり、データがグローバルに見えるという意味ではありません。データの可視範囲はあくまでセッションローカルです。

2種類の GTT:ON COMMIT DELETE ROWS と ON COMMIT PRESERVE ROWS

オプション データの生存スコープ COMMIT 時の動作 セッション終了時
ON COMMIT DELETE ROWS(デフォルト) トランザクション単位 全データが自動削除される データ削除(未コミット分も)
ON COMMIT PRESERVE ROWS セッション単位 データは保持される 全データが自動削除される
ON COMMIT DELETE ROWS:トランザクションスコープの GTT
-- ON COMMIT DELETE ROWS: COMMIT または ROLLBACK でデータが削除される
-- ストアドプロシージャ内の一時計算結果を保持し、プロシージャ終了と同時に消したい場合に使う
CREATE GLOBAL TEMPORARY TABLE gtt_calc_work (
    row_id     NUMBER,
    base_val   NUMBER,
    result_val NUMBER
) ON COMMIT DELETE ROWS;   -- デフォルト。省略可

-- 使い方:
BEGIN
    -- トランザクション内でデータを INSERT
    INSERT INTO gtt_calc_work VALUES (1, 100, NULL);
    INSERT INTO gtt_calc_work VALUES (2, 200, NULL);

    -- 計算処理
    UPDATE gtt_calc_work SET result_val = base_val * 1.1;

    -- 最終結果を別テーブルに反映
    INSERT INTO final_results SELECT result_val FROM gtt_calc_work;
    COMMIT;   -- ← ここで gtt_calc_work のデータが自動削除される

    -- COMMIT 後、gtt_calc_work は空
    SELECT COUNT(*) FROM gtt_calc_work;   -- 0件
END;
/
ON COMMIT PRESERVE ROWS:セッションスコープの GTT
-- ON COMMIT PRESERVE ROWS: セッションが続く限りデータが保持される
-- 複数のトランザクションにまたがる中間データを蓄積したい場合に使う
CREATE GLOBAL TEMPORARY TABLE gtt_session_work (
    step_no    NUMBER,
    step_name  VARCHAR2(100),
    processed  NUMBER DEFAULT 0
) ON COMMIT PRESERVE ROWS;

-- セッション内で複数の COMMIT をまたいでデータが残る
INSERT INTO gtt_session_work VALUES (1, 'データ取込', 0);
COMMIT;   -- ← データは削除されない

INSERT INTO gtt_session_work VALUES (2, '変換処理', 0);
COMMIT;   -- ← データは削除されない

SELECT COUNT(*) FROM gtt_session_work;   -- 2件(保持されている)

-- セッションを切断した瞬間にデータが全削除される(次回接続時は0件)

セッション間のデータ分離

GTT に INSERT したデータは自分のセッションからしか見えません。同じ GTT に別セッションが INSERT しても、お互いのデータは見えないため、セッションを意識したロック競合が発生しません。

セッション間の分離の確認
-- セッション A が INSERT
-- セッション A:
INSERT INTO gtt_session_work VALUES (99, 'セッションAのデータ', 0);
SELECT COUNT(*) FROM gtt_session_work;   -- 1件(セッションAには見える)

-- セッション B(同時接続):
SELECT COUNT(*) FROM gtt_session_work;   -- 0件(セッションBには見えない)
-- → GTT は行レベルのロックも発生しない(データが独立しているため)

-- 実務上の利点:
-- バッチ処理を複数セッションで並列実行しても、それぞれが独立した作業領域を持てる
GTT へのロック競合は発生しない
通常のテーブルでは UPDATE 時に行ロックが発生し、他セッションが待機することがあります。GTT はデータが各セッション専用のため、複数のバッチジョブが同じ GTT 名を使っても互いに待機せず並列実行できます。高負荷なバッチを並列化する際のワークテーブルとして特に有効です。

GTT のインデックスと統計情報

GTT にインデックスを作成する
-- GTT にも通常のテーブルと同様にインデックスを作成できる
-- インデックスの定義は共有されるが、インデックスエントリ(データ)はセッション分離される
CREATE INDEX idx_gtt_work_row ON gtt_session_work (step_no);
CREATE INDEX idx_gtt_work_step ON gtt_session_work (step_name, processed);

-- インデックスはデータと同様に COMMIT または セッション終了で自動削除される
-- → 毎回 CREATE INDEX は不要。一度作ればテーブル定義と共に永続化される
GTT の統計情報をセッション単位で設定する
-- GTT はデータがセッションごとに変わるため、GATHER_TABLE_STATS が困難
-- DBMS_STATS.SET_TABLE_STATS で行数・ブロック数を手動設定するのが実務的

BEGIN
    DBMS_STATS.SET_TABLE_STATS(
        ownname   => USER,
        tabname   => 'GTT_SESSION_WORK',
        numrows   => 10000,   -- 予想される行数
        numblks   => 100      -- 予想ブロック数
    );
END;
/
-- → オプティマイザが GTT の行数を正しく見積もれるようになる
-- ※ Oracle 12c 以降では SESSION スコープの統計(セッションごとに自動収集)も利用可能

-- Oracle 12c 以降: GTT の統計をセッション単位で自動収集する設定
-- (GLOBAL_TEMP_TABLE_STATS を SESSION に設定すると、セッション内のデータに基づいて統計が更新される)
BEGIN
    DBMS_STATS.SET_TABLE_PREFS(
        ownname  => USER,
        tabname  => 'GTT_SESSION_WORK',
        pname    => 'GLOBAL_TEMP_TABLE_STATS',
        pval     => 'SESSION'   -- SESSION(デフォルト)または SHARED を指定
    );
END;
/

パフォーマンス特性:undo/redo が少ない理由

GTT は通常テーブルと異なり、一時セグメント(Temp Segment)に書き込まれます。

特性 通常テーブル GTT
書き込み先 undo 付きのデータブロック 一時セグメント(undo 大幅削減)
redo ログ 生成される 大幅に削減される
undo 生成 ROLLBACK 用に生成 最小限(構造変更のみ)
ROLLBACK コスト undo から復元するため重い 一時セグメントの破棄のみで軽い
COMMIT コスト redo をフラッシュ 削減される

大量データを一時的に処理する場合、通常テーブルに書いて最後に DELETE するよりも GTT を使う方がundo 表領域の消費と redo ログの生成を大幅に抑えられます。

GTT の DDL(TRUNCATE・ALTER・DROP)

GTT に対する DDL の動作
-- TRUNCATE: 自セッションのデータのみ削除(他セッションのデータは影響なし)
TRUNCATE TABLE gtt_session_work;

-- DROP TABLE: テーブル定義ごと削除(全セッションのデータが失われる)
-- 他セッションが使用中でも DROP は実行できる
-- → 実行中のバッチがある場合は DROP しないよう注意
DROP TABLE gtt_session_work;

-- ALTER TABLE: 列追加などのスキーマ変更は通常テーブルと同様に可能
-- ただし他セッションがデータを持っている状態での ALTER は影響を与える可能性がある
ALTER TABLE gtt_calc_work ADD (calc_status VARCHAR2(20) DEFAULT 'PENDING');

GTT・WITH句・ワークテーブルの使い分け

方法 用途・特徴 推奨するケース
WITH句(CTE) SQL 内の中間結果。クエリが終わると消える 1つのクエリ内で使いまわす中間計算
GTT(ON COMMIT DELETE ROWS) トランザクション内で複数の DML に使いまわす ストアドプロシージャで複数の UPDATE にまたがる中間データ
GTT(ON COMMIT PRESERVE ROWS) セッション全体で蓄積・参照する 複数のバッチステップをまたぐ大量データの一時保持
通常テーブル(ワークテーブル) 複数セッション間でデータを共有する必要がある バッチ間の引き継ぎデータ・エラー明細の永続保存

実務パターン:バッチ処理中間データの一時保持

ETL バッチで GTT を使う実務パターン
-- ステップ1:受注データ受け取り用 GTT(ON COMMIT PRESERVE ROWS でバッチ全体で保持)
CREATE GLOBAL TEMPORARY TABLE gtt_incoming_orders (
    order_id     NUMBER,
    customer_id  NUMBER,
    amount       NUMBER,
    status       VARCHAR2(20),
    err_msg      VARCHAR2(500)
) ON COMMIT PRESERVE ROWS;

-- ステップ2:バッチプロシージャ内での利用
CREATE OR REPLACE PROCEDURE batch_process_orders AS
    v_err_count NUMBER := 0;
BEGIN
    -- 外部表からデータを GTT に取り込む
    INSERT INTO gtt_incoming_orders (order_id, customer_id, amount, status)
    SELECT order_id, customer_id, amount, 'PENDING'
    FROM ext_orders_staging;   -- 外部ファイルから読み込んだデータ
    COMMIT;

    -- バリデーション:金額が負のものをエラーに
    UPDATE gtt_incoming_orders
    SET status  = 'ERROR',
        err_msg = '金額が0以下です'
    WHERE amount <= 0;
    COMMIT;

    -- 正常データのみ本テーブルに登録
    INSERT INTO orders (order_id, customer_id, amount)
    SELECT order_id, customer_id, amount
    FROM gtt_incoming_orders
    WHERE status = 'PENDING';

    -- エラー件数確認
    SELECT COUNT(*) INTO v_err_count
    FROM gtt_incoming_orders WHERE status = 'ERROR';

    COMMIT;

    IF v_err_count > 0 THEN
        -- エラー明細を永続テーブルにコピー(GTT はセッション終了で消えるため)
        INSERT INTO order_errors
        SELECT order_id, err_msg, SYSDATE FROM gtt_incoming_orders WHERE status = 'ERROR';
        COMMIT;
    END IF;

    DBMS_OUTPUT.PUT_LINE('処理完了: エラー' || v_err_count || '件');
    -- セッション終了時に gtt_incoming_orders は自動削除される
END;
/

よくある誤解と注意点

  • 「テンポラリ表領域が必要」は誤り:GTT のデータはデフォルトのテンポラリ表領域に格納されるため、専用の CREATE TEMPORARY TABLESPACE は不要です。通常の TEMP 表領域が使われます
  • テーブル定義は永続的:GTT は定義を DROP するまで残ります。データだけが自動削除されます
  • フラッシュバック・クエリは使えない:GTT は undo に書き込まれないため、AS OF TIMESTAMP での過去参照はできません
  • 分散トランザクション(DBLINK)では制限あり:GTT をリモートの DB リンク経由で参照することはできません
  • エラー明細は別テーブルに保存:GTT のデータはセッション終了で消えます。エラー情報など後で確認が必要なデータは永続テーブルに COMMIT しておく必要があります

まとめ

  • ON COMMIT DELETE ROWS:COMMIT でデータ削除。ストアドプロシージャ内の一時計算に最適
  • ON COMMIT PRESERVE ROWS:セッション終了でデータ削除。バッチ全体の中間データ保持に最適
  • セッション分離:自セッションのデータのみ見える。並列バッチでロック競合なし
  • パフォーマンス:undo/redo 生成が少なく、大量データの一時処理で効果的
  • 統計情報:DBMS_STATS.SET_TABLE_STATS で行数を手動設定して実行計画を安定させる
  • エラーデータは永続テーブルへ:GTT のデータはセッション終了で消えるため、確認が必要なデータは永続化する

GTT の中間データを集計・整形するにはサブクエリ完全ガイドのインラインビューや WITH句(CTE)も組み合わせると効果的です。大量データ処理での undo 表領域の肥大化が問題になる場合はTEMP表領域の肥大化の原因とクリーンアップ方法も参照してください。