ORA-12838: cannot read/modify an object after modifying it in parallel は、並列DMLやダイレクト・パスINSERTで表を変更したあと、同じトランザクション内でその表を再度SELECT、UPDATE、DELETE、MERGEしようとした時に発生するエラーです。大量ロードや高速INSERTのあとに件数確認、後続UPDATE、同じ表へのMERGEを続けた時に出やすいです。
結論から言うと、並列DMLまたはAPPENDで変更した表は、COMMITまたはROLLBACKするまで同じトランザクション内で再アクセスできないと考えるのが安全です。Oracle公式のエラー説明でも、対処はトランザクションをCOMMITまたはROLLBACKしてから再アクセスすることとされています。並列DMLの基本は Oracle並列実行完全ガイド、APPENDの基本は ダイレクト・パス・インサート完全ガイド もあわせて確認してください。
- ORA-12838が発生する条件
- 並列DMLとAPPENDで再現するSQL
- COMMITで解消する理由
- APPENDを外す、処理を分ける、一時表を使う対策
- MERGE、CTAS、Data Pump、PL/SQLバッチでの注意点
- 本番投入前のチェックリスト
重複確認結果
本番DBで ORA-12838、英語メッセージ、並列DML、APPEND後などを検索しましたが、ORA-12838単独記事は見つかりませんでした。既存には並列実行、APPEND、MERGE、SQLヒント、LOG ERRORSの記事があるため、この記事はそれらをつなぐエラー対処記事として作成しています。
最初に結論:COMMITするまで同じ表に触らない
ORA-12838の一番シンプルな対処は、対象表を変更した直後に COMMIT または ROLLBACK でトランザクションを終わらせることです。その後でSELECTやUPDATEを行えば、エラーは解消します。
COMMIT してから、件数確認や後続処理を実行します。COMMIT境界の設計は 大量データ処理のコミット頻度とUNDO最適化、PL/SQLバッチ全体の性能設計は PL/SQLパフォーマンス改善プレイブック が関連します。
発生条件の判断フロー
ORA-12838は、対象表に何をした直後なのかを見ると切り分けやすいです。単にSELECTが失敗したように見えても、直前のDMLがAPPENDや並列DMLだった、という流れが多いです。
ALTER SESSION ENABLE PARALLEL DML が有効な状態でUPDATE、DELETE、MERGEした可能性があります。APPENDと並列DMLの切り分け
APPENDと並列DMLは一緒に使われることもありますが、切り分けでは分けて考えると原因を追いやすくなります。APPENDはダイレクト・パスINSERTを狙うヒント、並列DMLはDMLを並列実行するセッション設定またはヒントです。
APPENDだけで起きる形
-- 並列DMLを明示していなくても、APPEND後に同じ表を読むと問題になることがある INSERT /*+ APPEND */ INTO load_work SELECT * FROM load_source; SELECT COUNT(*) FROM load_work; -- ORA-12838の可能性 ROLLBACK;
並列DMLで起きる形
ALTER SESSION ENABLE PARALLEL DML; DELETE /*+ PARALLEL(load_work 4) */ FROM load_work WHERE batch_id = :batch_id; SELECT COUNT(*) FROM load_work; -- ORA-12838の可能性 ROLLBACK; ALTER SESSION DISABLE PARALLEL DML;
どちらも対処の中心は、同じ表に再アクセスする前にトランザクションを区切ることです。それができない処理では、APPENDや並列DMLを外すか、対象表を分けます。
ORA-12838が起きる典型パターン
ORA-12838は、並列DMLやダイレクト・パスINSERTによって変更された表に、同一トランザクション内で再アクセスした時に起きます。読み取りだけでも対象になります。つまり、INSERT後に SELECT COUNT(*) しただけでも失敗することがあります。
APPEND INSERT後にSELECTする
INSERT /*+ APPEND */ INTO sales_work SELECT * FROM sales_source WHERE sales_date >= DATE '2026-01-01'; -- 同じトランザクション内で同じ表を読むため ORA-12838 SELECT COUNT(*) FROM sales_work;
並列DML後にUPDATEする
ALTER SESSION ENABLE PARALLEL DML; UPDATE /*+ PARALLEL(sales_work 4) */ sales_work SET load_status = 'DONE' WHERE load_status = 'NEW'; -- 同じ表を再度変更するため ORA-12838 UPDATE sales_work SET checked_flag = 'Y' WHERE load_status = 'DONE';
MERGE後に件数確認する
ALTER SESSION ENABLE PARALLEL DML; MERGE /*+ PARALLEL(t 4) */ INTO customer_summary t USING customer_summary_stage s ON (t.customer_id = s.customer_id) WHEN MATCHED THEN UPDATE SET t.total_amount = s.total_amount WHEN NOT MATCHED THEN INSERT (customer_id, total_amount) VALUES (s.customer_id, s.total_amount); -- 同じトランザクション内で対象表を読むと ORA-12838 SELECT COUNT(*) FROM customer_summary;
MERGE自体の設計は Oracle MERGE文完全ガイド と PL/SQL MERGE文でUPSERTを高速・安全に実装 も参考になります。
なぜCOMMITで解消するのか
並列DMLやダイレクト・パスINSERTは、通常のDMLとは異なる内部処理で表を変更します。処理後、同じトランザクション内では対象表に対する読み書きが制限されます。COMMITまたはROLLBACKでトランザクションを終わらせると、その制限が解除されます。
INSERT /*+ APPEND */ INTO sales_work SELECT * FROM sales_source WHERE sales_date >= DATE '2026-01-01'; COMMIT; -- COMMIT後なら読める SELECT COUNT(*) FROM sales_work;
ただし、COMMITを入れればよい、で終わらせない方が安全です。途中でCOMMITすると、後続処理で失敗した場合に一括ROLLBACKできません。再実行設計、ステータス列、処理ログ、エラー行の隔離まで考える必要があります。
対策1:COMMIT境界を明確に分ける
最も分かりやすい設計は、ロード、検証、反映をトランザクション単位で分けることです。APPENDや並列DMLを使う処理は高速化専用のステップにし、COMMIT後に検証や後続DMLへ進みます。
-- Step 1: 高速ロード INSERT /*+ APPEND */ INTO sales_stage SELECT * FROM external_sales; COMMIT; -- Step 2: 件数確認 SELECT COUNT(*) FROM sales_stage; -- Step 3: 本表へ反映 MERGE INTO sales t USING sales_stage s ON (t.sales_id = s.sales_id) WHEN MATCHED THEN UPDATE SET t.amount = s.amount WHEN NOT MATCHED THEN INSERT (sales_id, amount) VALUES (s.sales_id, s.amount); COMMIT;
この形なら、APPEND後に同じ表を読むタイミングはCOMMIT後になります。ステップごとに処理ログを残しておくと、再実行もしやすくなります。
対策2:同じトランザクションで読む必要があるならAPPENDを外す
処理の途中で同じ表を確認し、その結果によってROLLBACKする必要があるなら、APPENDや並列DMLを使わない方が設計が単純です。高速化よりもトランザクションの一貫性が重要なケースです。
-- APPENDを使わない通常INSERT INSERT INTO sales_work SELECT * FROM sales_source WHERE sales_date >= DATE '2026-01-01'; -- 同じトランザクション内で確認できる SELECT COUNT(*) FROM sales_work; -- 条件に応じてCOMMIT/ROLLBACKを選べる COMMIT;
APPENDヒントやPARALLELヒントは速くなる可能性がありますが、制約も増えます。ヒント句全般は Oracle SQLヒント句完全ガイド で整理しています。
対策3:ステージング表を分ける
大量ロードと検証を両立したい場合は、ステージング表を明確に分けるのが実務的です。高速ロード用の表、検証用の表、最終反映先の表を分けることで、同じ表を同じトランザクションで触る場面を減らせます。
エラー行を逃がす設計は DBMS_ERRLOGとLOG ERRORSの使い方、FORALLで失敗行を拾う設計は FORALL SAVE EXCEPTIONSで失敗行を個別ログ化する方法 が関連します。
対策4:並列DMLを必要なセッションだけで有効化する
並列DMLは ALTER SESSION ENABLE PARALLEL DML で有効化します。便利ですが、セッション全体に影響します。バッチ内の一部処理だけで使いたい場合、どこで有効化し、どこで無効化するかを明確にします。
ALTER SESSION ENABLE PARALLEL DML; INSERT /*+ PARALLEL(sales_stage 4) APPEND */ INTO sales_stage SELECT /*+ PARALLEL(src 4) */ * FROM sales_source src; COMMIT; ALTER SESSION DISABLE PARALLEL DML;
並列度を上げすぎると、CPU、I/O、UNDO、TEMP、ロック待ちに影響します。実行中のSQL調査は 遅いSQLを特定する方法完全ガイド、Data Pumpのパラレルは Data Pumpのパラレル処理で高速化する方法 が関連します。
APPENDとNOLOGGINGの注意点
APPENDは高速ロードでよく使いますが、同時にロック、領域使用、REDO、バックアップ運用も意識します。NOLOGGINGを組み合わせる場合、障害時にバックアップから完全復旧できるかを確認します。
PL/SQLバッチでの安全な構成
PL/SQLの中でAPPENDや並列DMLを使う場合、処理の途中で同じ表を読んでいないかを確認します。特に、件数チェック、重複チェック、ログ出力、再処理判定のSQLが対象表を参照しているとORA-12838につながります。
BEGIN
INSERT /*+ APPEND */ INTO sales_stage
SELECT *
FROM sales_source
WHERE batch_id = :batch_id;
COMMIT;
-- COMMIT後に検証する
validate_sales_stage(p_batch_id => :batch_id);
merge_sales(p_batch_id => :batch_id);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
/
途中COMMITが入るため、例外時に全体を元に戻す設計ではありません。ステップ単位の再実行、処理済みフラグ、バッチID、ログを用意します。
バッチ修正例:件数確認をCOMMIT後へ移す
-- NG: APPEND直後に同じ表を件数確認する INSERT /*+ APPEND */ INTO import_stage SELECT * FROM import_source WHERE batch_id = :batch_id; -- SELECT COUNT(*) FROM import_stage; -- ここでORA-12838 -- OK: COMMIT後に確認する COMMIT; SELECT COUNT(*) FROM import_stage WHERE batch_id = :batch_id;
バッチ修正例:検証用の表を分ける
-- 高速ロード専用の表 INSERT /*+ APPEND */ INTO import_stage_raw SELECT * FROM import_source WHERE batch_id = :batch_id; COMMIT; -- 検証済みデータを別表へ移す INSERT INTO import_stage_valid SELECT * FROM import_stage_raw WHERE batch_id = :batch_id AND required_col IS NOT NULL; COMMIT;
切り分けSQL
ORA-12838が出たSQLを見つけるには、エラーを出したバッチログ、アプリログ、SQL_ID、セッション情報を突き合わせます。並列DMLやAPPENDを使っている箇所を探します。
SELECT sql_id,
parsing_schema_name,
executions,
sql_text
FROM v$sql
WHERE UPPER(sql_text) LIKE '%APPEND%'
OR UPPER(sql_text) LIKE '%PARALLEL%'
OR UPPER(sql_text) LIKE '%ENABLE PARALLEL DML%'
ORDER BY last_active_time DESC
FETCH FIRST 50 ROWS ONLY;
SELECT s.sid,
s.serial#,
s.username,
s.module,
s.action,
s.sql_id,
q.sql_text
FROM v$session s
LEFT JOIN v$sql q
ON q.sql_id = s.sql_id
WHERE s.username IS NOT NULL
ORDER BY s.logon_time DESC;
アプリ側からMODULE/ACTIONを設定しておくと、どのバッチステップで起きたか追いやすくなります。
避けたい対応
本番投入前チェックリスト
- APPENDまたは並列DMLを使うSQLを一覧化している
- その直後に同じ表をSELECT、UPDATE、DELETE、MERGEしていない
- COMMIT境界が業務整合性と再実行設計に合っている
- 同じトランザクションで確認が必要な処理ではAPPENDを外している
- ステージング表、本表、エラー表の役割を分けている
- 失敗時にどのステップから再実行するか決めている
- NOLOGGINGを使う場合、バックアップ・リカバリ方針を確認している
- 並列度がCPU、I/O、TEMP、UNDOに与える影響を測定している
- SQL_ID、MODULE、ACTIONで原因SQLを追えるようにしている
- 本番反映後、エラー件数とバッチ実行時間を監視する
まとめ
ORA-12838は、並列DMLやAPPENDで変更した表を、同じトランザクション内で再度読んだり変更したりした時に発生します。最短の対処はCOMMITまたはROLLBACKですが、処理途中にCOMMITを入れるなら、再実行設計と整合性設計も必要です。
高速ロードにはAPPENDや並列DMLが有効ですが、ロード、検証、反映を同じ表・同じトランザクションに詰め込むとORA-12838につながります。ステージング表、COMMIT境界、バッチID、エラー表を使い、速さと運用しやすさを両立させましょう。

