数千万〜数億件のテーブルに対して UPDATE や DELETE を実行すると、ロールバックセグメント(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 の手順は次のとおりです。
- CREATE_TASK:タスク(作業単位)を作成する
- CREATE_CHUNKS_BY_ROWID(または BY_NUMBER_COL):テーブルをチャンクに分割する
- RUN_TASK:各チャンクを処理する PL/SQL ブロックを並列実行する
- 確認・エラー対応:失敗チャンクを確認して RESUME_TASK で再実行する
- 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 完全ガイドも参考にしてください。