【Oracle】ダイレクト・パス・インサート(APPEND)完全ガイド|APPEND ヒント・NOLOGGING・大量データの高速 INSERT・CTAS まで解説

大量データの一括 INSERT は、通常の行単位インサートに比べて REDO ログ・バッファキャッシュの使用量が多く、パフォーマンスのボトルネックになりやすい処理です。

ダイレクト・パス・インサート(Direct Path Insert)は、バッファキャッシュを経由せずにデータを直接データファイルに書き込む方式です。/*+ APPEND */ ヒントと NOLOGGING を組み合わせることで、通常の INSERT に比べて大幅に高速化できます。

この記事でわかること

  • ダイレクト・パス・インサートの仕組みと通常 INSERT との違い
  • /*+ APPEND */ ヒントの使い方と効果が出る条件
  • NOLOGGING で REDO ログ生成を最小化する方法
  • CREATE TABLE AS SELECT(CTAS)での NOLOGGING の使い方
  • ダイレクト・パス・インサート後の制約(コミット前の SELECT・DML が制限される)
  • NOLOGGING 後に必要な対応(REDO ログなしブロックの管理)
スポンサーリンク

通常の INSERT とダイレクト・パス・インサートの違い

項目 通常の INSERT ダイレクト・パス・インサート
書き込み先 バッファキャッシュ → 非同期でデータファイルへ書き出し データファイルの HWM(High Water Mark)以降に直接書き込み
REDO ログ 全変更を記録 NOLOGGING 指定時はほとんど記録しない
空きブロックの再利用 既存の空きブロックを使う HWM を越えた新規領域にのみ書き込む(既存空きブロックは使わない)
テーブルロック 行ロックのみ テーブルに排他ロックがかかる
適した場面 少量データ・OLTP 大量データの一括 INSERT・バッチ処理

APPEND ヒントでダイレクト・パス・インサートを使う

INSERT … SELECT に APPEND ヒントを付ける
-- 通常の INSERT(バッファキャッシュ経由)
INSERT INTO sales_archive
SELECT * FROM sales WHERE sale_date < DATE '2023-01-01';
COMMIT;

-- ダイレクト・パス・インサート(HWM に直接書き込む)
INSERT /*+ APPEND */ INTO sales_archive
SELECT * FROM sales WHERE sale_date < DATE '2023-01-01';
COMMIT;
-- コミット後に INSERT が確定する(コミット前は同テーブルへのアクセスが制限される)

-- APPEND と PARALLEL を組み合わせる(最大限の高速化)
INSERT /*+ APPEND PARALLEL(sales_archive, 4) */ INTO sales_archive
SELECT * FROM sales WHERE sale_date < DATE '2023-01-01';
COMMIT;
APPEND インサート後のコミット前の制約

  • ダイレクト・パス・インサート後、コミットまでの間に同じテーブルへの SELECT や DML はエラー(ORA-12838)になる
  • コミットまたはロールバック後は通常通りアクセスできる
  • 複数の APPEND INSERT を連続して実行する場合も、各 INSERT の間にコミットが必要

NOLOGGING で REDO ログ生成を最小化する

NOLOGGING を指定すると、ダイレクト・パス・インサートでREDO ログをほぼ生成しないため、LGWR の負荷が大幅に減少します。ただし NOLOGGING ブロックはアーカイブに含まれないため、インスタンスリカバリには使えますがメディアリカバリが必要な障害では失われる可能性があります。

テーブルを NOLOGGING に変更してダイレクト・パス・インサートを実行する
-- テーブルを一時的に NOLOGGING に変更する
ALTER TABLE sales_archive NOLOGGING;

-- APPEND ヒントを使った INSERT(NOLOGGING 効果が最大化される)
INSERT /*+ APPEND */ INTO sales_archive
SELECT * FROM sales WHERE sale_date < DATE '2023-01-01';
COMMIT;

-- 作業後は必ず LOGGING に戻す(Data Guard・バックアップのため)
ALTER TABLE sales_archive LOGGING;

-- NOLOGGING ブロックが存在するかどうかを確認する(バックアップ後に実行)
SELECT segment_name, tablespace_name
FROM V$NONLOGGED_BLOCK
ORDER BY segment_name;
-- V$NONLOGGED_BLOCK にエントリがある場合はバックアップの再取得を検討する

CREATE TABLE AS SELECT(CTAS)と NOLOGGING

CTAS は最も効率的なデータコピー方法のひとつです。NOLOGGING を指定することで REDO ログを最小化できます。

NOLOGGING を指定した CTAS(CREATE TABLE AS SELECT)
-- NOLOGGING を指定した CTAS(最も高速なテーブルコピー)
CREATE TABLE sales_archive_2022 NOLOGGING
AS SELECT * FROM sales WHERE EXTRACT(YEAR FROM sale_date) = 2022;

-- PARALLEL を加えてさらに高速化する
CREATE TABLE sales_archive_2022 NOLOGGING PARALLEL 4
AS SELECT * FROM sales WHERE EXTRACT(YEAR FROM sale_date) = 2022;

-- CTAS 後にインデックスを作成する(NOLOGGING でインデックスも高速構築)
CREATE INDEX sales_arch_date_ix ON sales_archive_2022(sale_date) NOLOGGING PARALLEL 4;

-- CTAS 後にテーブルを LOGGING に変更する(通常運用に戻す)
ALTER TABLE sales_archive_2022 LOGGING;
ALTER INDEX sales_arch_date_ix LOGGING;

-- 統計情報を収集する
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname  => 'HR',
        tabname  => 'SALES_ARCHIVE_2022',
        cascade  => TRUE   -- インデックス統計も収集
    );
END;
/

HWM(High Water Mark)と領域管理

ダイレクト・パス・インサート後の HWM と使用量を確認する
-- テーブルのセグメント情報(HWM の状態を間接的に確認)
SELECT
    t.table_name,
    t.num_rows,
    s.bytes / 1024 / 1024 AS segment_mb,
    t.blocks,
    t.empty_blocks,   -- 未使用ブロック数(ANALYZE が必要)
    t.avg_row_len
FROM DBA_TABLES t
JOIN DBA_SEGMENTS s ON t.owner = s.owner AND t.table_name = s.segment_name
WHERE t.owner = 'SALES' AND t.table_name = 'SALES_ARCHIVE';

-- DBMS_SPACE でブロックの使用状況を確認する
DECLARE
    v_total_blocks   NUMBER;
    v_total_bytes    NUMBER;
    v_unused_blocks  NUMBER;
    v_unused_bytes   NUMBER;
    v_last_extf      NUMBER;
    v_last_extb      NUMBER;
    v_last_used_block NUMBER;
BEGIN
    DBMS_SPACE.UNUSED_SPACE(
        segment_owner    => 'SALES',
        segment_name     => 'SALES_ARCHIVE',
        segment_type     => 'TABLE',
        total_blocks     => v_total_blocks,
        total_bytes      => v_total_bytes,
        unused_blocks    => v_unused_blocks,
        unused_bytes     => v_unused_bytes,
        last_used_extent_file_id  => v_last_extf,
        last_used_extent_block_id => v_last_extb,
        last_used_block  => v_last_used_block
    );
    DBMS_OUTPUT.PUT_LINE('総ブロック数: ' || v_total_blocks);
    DBMS_OUTPUT.PUT_LINE('未使用ブロック数: ' || v_unused_blocks);
END;
/

まとめ

  • /*+ APPEND */:バッファキャッシュをバイパスしてデータファイルへ直接書き込む。大量データの INSERT に効果的
  • NOLOGGING:ダイレクト・パス・インサートと組み合わせることで REDO ログをほぼ生成しない。後で LOGGING に戻すことを忘れずに
  • コミット前の制約:APPEND INSERT 後のコミット前は ORA-12838 が発生するため、各 INSERT 後にコミットが必要
  • CTAS + NOLOGGING + PARALLEL:最も効率的な大規模テーブルコピーの組み合わせ
  • V$NONLOGGED_BLOCK:NOLOGGING 後のメディアリカバリ範囲を確認できる。バックアップを再取得することが推奨される

並列実行との組み合わせは 並列実行完全ガイドを参照してください。REDO ログの仕組みは オンライン REDO ログ完全ガイドも参照してください。