【Oracle】DBMS_PARALLEL_EXECUTE完全ガイド|大規模テーブルをチャンク分割して並列バッチ更新・エラー再実行まで解説

数千万〜数億件のテーブルに対して UPDATEDELETE を実行すると、ロールバックセグメント(UNDO)を大量に消費して ORA-01555 が発生したり、長時間のロックでオンラインサービスに影響が出たりします。

DBMS_PARALLEL_EXECUTE は、大規模テーブルの DML をROWID 範囲またはキー値の範囲(チャンク)に分割して、並列ジョブとして実行するパッケージです。各チャンクは独立してコミットするため UNDO 消費が抑えられ、失敗したチャンクだけを再実行することもできます。

この記事でわかること

  • DBMS_PARALLEL_EXECUTE の基本フロー(CREATE_TASK → CHUNKS → RUN_TASK)
  • ROWID 範囲によるチャンク分割(CREATE_CHUNKS_BY_ROWID)の使い方
  • 連番キーによるチャンク分割(CREATE_CHUNKS_BY_NUMBER_COL)の使い方
  • 並列度(parallel_level)の設定と DBMS_SCHEDULER ジョブへの割り当て
  • エラーチャンクの確認と RESUME_TASK による再実行
  • USER_PARALLEL_EXECUTE_TASKS / CHUNKS ビューでの進捗モニタリング
スポンサーリンク

DBMS_PARALLEL_EXECUTE の基本フロー

DBMS_PARALLEL_EXECUTE を使った大規模 DML の手順は次のとおりです。

  1. CREATE_TASK:タスク(作業単位)を作成する
  2. CREATE_CHUNKS_BY_ROWID(または BY_NUMBER_COL):テーブルをチャンクに分割する
  3. RUN_TASK:各チャンクを処理する PL/SQL ブロックを並列実行する
  4. 確認・エラー対応:失敗チャンクを確認して RESUME_TASK で再実行する
  5. DROP_TASK:タスクを削除してリソースを解放する
ROWID チャンク分割による大規模 UPDATE の基本パターン
-- ステップ1: タスクを作成する
EXEC DBMS_PARALLEL_EXECUTE.CREATE_TASK(task_name => 'update_status_task');

-- ステップ2: テーブルを ROWID 範囲でチャンク分割する
-- chunk_size: 1チャンクあたりの ROWID 数(目安: 10000〜100000)
EXEC DBMS_PARALLEL_EXECUTE.CREATE_CHUNKS_BY_ROWID(
    task_name   => 'update_status_task',
    table_owner => 'HR',
    table_name  => 'ORDERS',
    by_row      => TRUE,        -- TRUE: 行数単位, FALSE: ブロック数単位
    chunk_size  => 10000        -- 1チャンクあたり 10,000 行
);

-- チャンクが作成されたか確認する
SELECT chunk_id, status, start_rowid, end_rowid
FROM   USER_PARALLEL_EXECUTE_CHUNKS
WHERE  task_name = 'update_status_task'
ORDER  BY chunk_id;

-- ステップ3: 各チャンクを処理する SQL を定義して実行する
-- SQL には :start_id(開始 ROWID)と :end_id(終了 ROWID)のプレースホルダを使う
DECLARE
    v_sql VARCHAR2(1000);
BEGIN
    v_sql := 'UPDATE orders
              SET    status = ''ARCHIVED''
              WHERE  order_date < DATE ''2023-01-01''
                AND  ROWID BETWEEN :start_id AND :end_id';

    DBMS_PARALLEL_EXECUTE.RUN_TASK(
        task_name      => 'update_status_task',
        sql_stmt       => v_sql,
        language_flag  => DBMS_SQL.NATIVE,
        parallel_level => 4        -- 並列度(同時実行するジョブ数)
    );
END;
/

-- ステップ4: タスクのステータスを確認する
SELECT status FROM USER_PARALLEL_EXECUTE_TASKS WHERE task_name = 'update_status_task';
-- FINISHED: すべてのチャンクが成功
-- FINISHED_WITH_ERROR: 一部チャンクが失敗

-- ステップ5: 完了後にタスクを削除する
EXEC DBMS_PARALLEL_EXECUTE.DROP_TASK(task_name => 'update_status_task');

連番キーによるチャンク分割(CREATE_CHUNKS_BY_NUMBER_COL)

ROWID ではなく数値型の主キーやシーケンス列でチャンクを分割したい場合はCREATE_CHUNKS_BY_NUMBER_COL を使います。ROWID よりも分割範囲が直感的で、後から進捗を確認しやすくなります。

NUMBER 列によるチャンク分割と並列処理
-- CREATE_TASK(同様に実行)
EXEC DBMS_PARALLEL_EXECUTE.CREATE_TASK(task_name => 'update_orders_by_id');

-- 数値列(order_id)でチャンク分割する
EXEC DBMS_PARALLEL_EXECUTE.CREATE_CHUNKS_BY_NUMBER_COL(
    task_name   => 'update_orders_by_id',
    table_owner => 'HR',
    table_name  => 'ORDERS',
    table_column => 'ORDER_ID',    -- 分割に使う数値列(主キーや連番が適切)
    chunk_size  => 50000            -- 1チャンクあたり 50,000 件
);

-- チャンクの内容を確認する
SELECT chunk_id, status,
       start_id AS start_order_id,  -- 開始 ORDER_ID
       end_id   AS end_order_id     -- 終了 ORDER_ID
FROM   USER_PARALLEL_EXECUTE_CHUNKS
WHERE  task_name = 'update_orders_by_id'
ORDER  BY chunk_id;

-- NUMBER 列チャンクでは :start_id / :end_id が数値になる
DECLARE
    v_sql VARCHAR2(1000);
BEGIN
    v_sql := 'UPDATE orders
              SET    discount_applied = ''Y''
              WHERE  order_id BETWEEN :start_id AND :end_id
                AND  total_amount > 100000';

    DBMS_PARALLEL_EXECUTE.RUN_TASK(
        task_name      => 'update_orders_by_id',
        sql_stmt       => v_sql,
        language_flag  => DBMS_SQL.NATIVE,
        parallel_level => 8        -- より多くの並列ジョブで処理する
    );
END;
/

失敗チャンクの確認と RESUME_TASK による再実行

エラーチャンクの確認と再実行
-- 失敗したチャンクを確認する
SELECT chunk_id, status, error_code, error_message,
       start_id, end_id
FROM   USER_PARALLEL_EXECUTE_CHUNKS
WHERE  task_name = 'update_orders_by_id'
  AND  status = 'PROCESSED_WITH_ERROR'
ORDER  BY chunk_id;
-- status の値:
-- UNASSIGNED        : 未処理(初期状態)
-- ASSIGNED          : 処理中(ジョブに割り当て済み)
-- PROCESSED         : 処理完了(成功)
-- PROCESSED_WITH_ERROR: 処理失敗(error_message でエラー内容を確認する)

-- 失敗チャンクだけを再実行する(RESUME_TASK)
-- 成功済みチャンクはスキップされる
DECLARE
    v_sql VARCHAR2(1000);
BEGIN
    v_sql := 'UPDATE orders
              SET    discount_applied = ''Y''
              WHERE  order_id BETWEEN :start_id AND :end_id
                AND  total_amount > 100000';

    -- RUN_TASK の代わりに RESUME_TASK を使う(失敗チャンクのみ再実行)
    DBMS_PARALLEL_EXECUTE.RESUME_TASK(
        task_name      => 'update_orders_by_id',
        sql_stmt       => v_sql,
        language_flag  => DBMS_SQL.NATIVE,
        parallel_level => 4
    );
END;
/

-- 再実行後に全チャンクが成功したか確認する
SELECT status, COUNT(*) AS chunk_count
FROM   USER_PARALLEL_EXECUTE_CHUNKS
WHERE  task_name = 'update_orders_by_id'
GROUP  BY status;
-- status = PROCESSED だけになれば完了

タスク・チャンクのモニタリング

USER_PARALLEL_EXECUTE_TASKS / CHUNKS での進捗確認
-- タスク一覧と全体ステータスを確認する
SELECT task_name, status, start_ts, end_ts,
       job_prefix,    -- DBMS_SCHEDULER ジョブのプレフィックス
       chunk_size,
       chunk_type     -- ROWID or NUMBER_RANGE
FROM   USER_PARALLEL_EXECUTE_TASKS
ORDER  BY start_ts DESC;

-- 進捗をリアルタイムに確認する
SELECT
    COUNT(*) AS total_chunks,
    SUM(CASE status WHEN 'PROCESSED'             THEN 1 ELSE 0 END) AS done,
    SUM(CASE status WHEN 'PROCESSED_WITH_ERROR'  THEN 1 ELSE 0 END) AS errors,
    SUM(CASE status WHEN 'ASSIGNED'              THEN 1 ELSE 0 END) AS running,
    SUM(CASE status WHEN 'UNASSIGNED'            THEN 1 ELSE 0 END) AS pending
FROM   USER_PARALLEL_EXECUTE_CHUNKS
WHERE  task_name = 'update_orders_by_id';

-- 実行中の DBMS_SCHEDULER ジョブを確認する
SELECT job_name, state, last_start_date
FROM   USER_SCHEDULER_RUNNING_JOBS
WHERE  job_name LIKE 'CHUNKED_JOB%'  -- RUN_TASK が作成するジョブ名のパターン
ORDER  BY last_start_date;

-- 注意: parallel_level 個の DBMS_SCHEDULER ジョブが作成される
-- ジョブはチャンクを順番に取りに行きながら処理するキューイング方式

まとめ

  • DBMS_PARALLEL_EXECUTE の用途:数千万〜数億件の大規模テーブルを分割して UNDO 消費を抑えながら並列 DML を実行する。ORA-01555・長時間ロックの回避に有効
  • チャンク分割の2方式:CREATE_CHUNKS_BY_ROWID(ROWID 範囲)と CREATE_CHUNKS_BY_NUMBER_COL(数値キー範囲)。通常は ROWID 分割が汎用的で使いやすい
  • SQL プレースホルダ:RUN_TASK に渡す SQL には :start_id と :end_id を使う。ROWID 分割では ROWID 値、NUMBER 分割では数値が渡される
  • parallel_level:同時実行する DBMS_SCHEDULER ジョブ数。サーバーリソース(CPU 数・I/O 帯域)に応じて設定する
  • RESUME_TASK:失敗チャンクだけを再実行する。成功済みチャンクはスキップされるため冪等性が保たれる
  • モニタリング:USER_PARALLEL_EXECUTE_CHUNKS の status を定期的に確認して進捗を把握する

大規模 DML のパフォーマンス改善にはダイレクト・パス INSERT も有効です。ダイレクト・パス INSERT 完全ガイドも参照してください。UNDO セグメントの消費量の確認には ORA-01555 完全ガイドも参考にしてください。