【PL/SQL】COMMITとROLLBACKの正しい使い方|トランザクション境界設計・層別規約・例外統合・暗黙コミット罠・テンプレ3種

【PL/SQL】コミットとロールバックの正しい使い方 PL/SQL

PL/SQLでCOMMITROLLBACKを「正しく」使えていますか?基本構文を覚えるのは10分で済みますが、チームで開発するシステムで重要なのは「誰がいつCOMMITする責任を持つか」というトランザクション境界の設計です。これを曖昧にしたまま実装を進めると、部分コミット・暗黙コミット・例外時の整合性破壊といった、本番障害に直結する事故が頻発します。

「とりあえずプロシージャの最後にCOMMITを書く」という習慣は実は典型的なアンチパターンで、プロシージャを呼ぶ呼び出し元(別プロシージャやAPサーバ)がまだ別の処理を続けているのに勝手にコミットしてしまい、論理的なトランザクション単位を破壊します。正解は「プロシージャ内ではCOMMITしない・最上位の呼び出し元だけがCOMMITする」というトランザクション境界ルールです。

この記事ではPL/SQLでのCOMMIT/ROLLBACKの正しい使い方として、トランザクション境界の設計原則、層別コーディング規約、例外処理との統合パターン、暗黙コミットの罠、SAVEPOINTでの部分制御、Java/APサーバ連携時の注意、実装テンプレート、アンチパターン7選、FAQまで2026年版で整理します。

この記事でわかること

  • 「COMMITは最上位だけ」の原則と層別コーディング規約
  • 例外処理(EXCEPTION句)との統合パターン
  • 暗黙コミットの落とし穴(DDL・接続切断・特定操作)
  • SAVEPOINTを使った部分ロールバックの実装パターン
  • Java/JDBC/APサーバ連携時の境界の取り扱い
  • WHEN OTHERSとROLLBACKの正しい組み合わせ
  • 大量バッチでのコミット頻度の決め方
  • トランザクション設計3パターン(Service/Outbox/Saga)
  • 本番で踏むアンチパターン7選と修正テンプレート
スポンサーリンク

30秒でわかるCOMMIT/ROLLBACKの結論

忙しい読者向けの結論先出しです。

結論 理由・根拠
下位プロシージャはCOMMITしない 呼び出し元のトランザクション単位を破壊する。最上位だけがコミット責任
② 例外時はROLLBACKして再raise 例外を握りつぶすと整合性破壊と原因不明エラーの温床に
③ DDL/TRUNCATEは暗黙コミットを発火する 未COMMITのDMLが意図せず確定してしまう事故源
WHEN OTHERSではROLLBACK→RAISEが定石 処理を止めずに上位へ伝搬。沈黙する例外は最悪
⑤ Java/APから呼ぶ場合はPL/SQL側でCOMMITしない JDBCのトランザクション境界と二重制御で破綻する
⑥ 大量バッチは1000〜10000件単位でコミット 1件ずつはI/O律速、巨大単発はUNDO圧迫+ORA-01555のリスク
⑦ 部分制御が必要ならSAVEPOINT 「ここまでは残す」を保証しつつ局所ROLLBACKできる

トランザクション境界の設計|誰がCOMMITする責任を持つか

PL/SQLのCOMMIT/ROLLBACKで最も重要なのは「コミット責任を負うのは1人だけ」という原則です。複数の関数が勝手にCOMMITすると、上位処理の途中で勝手に確定されたり、失敗時のROLLBACKが効かない部分が残ったりします。

原則:最上位だけがCOMMITする

呼び出し階層の最上位の窓口(Webアプリから呼ばれるAPI関数、JOBから呼ばれる起動関数等)だけがCOMMIT/ROLLBACK文を書きます。内部のService層・Repository層・Util層は一切COMMITしない。これだけでトランザクション境界が一意に決まり、「気がついたら部分コミットされていた」という事故が消滅します。

層別のCOMMIT責任ルール

層別コーディング規約のテンプレート
-- ===========================================================
-- API層:トランザクション境界。COMMIT/ROLLBACKは「ここだけ」
-- ===========================================================
CREATE OR REPLACE PACKAGE BODY pkg_api_order AS
  PROCEDURE post_order(p_payload IN CLOB, p_result OUT CLOB) AS
  BEGIN
    -- ビジネス処理は下位レイヤに完全委譲
    pkg_order_service.create_order(p_payload, p_result);

    -- 全部成功したらここで確定(ここがコミット責任の所在)
    COMMIT;

  EXCEPTION
    WHEN OTHERS THEN
      ROLLBACK;            -- 失敗時は完全ロールバック
      pkg_util_log.error(SQLERRM);
      RAISE;               -- 例外を上位(クライアント)へ伝搬
  END;
END pkg_api_order;
/

-- ===========================================================
-- Service層/Repository層:COMMITは絶対に書かない
-- ===========================================================
CREATE OR REPLACE PACKAGE BODY pkg_order_service AS
  PROCEDURE create_order(p_payload IN CLOB, p_result OUT CLOB) AS
  BEGIN
    pkg_order_repo.insert_header(...);
    pkg_order_repo.insert_items(...);
    pkg_inventory_repo.decrement(...);
    -- ❌ ここにCOMMITやROLLBACKを書いてはいけない
    -- 失敗時は例外を伝搬させ、API層のEXCEPTION句で処理させる
  END;
END pkg_order_service;
/

CREATE OR REPLACE PACKAGE BODY pkg_order_repo AS
  PROCEDURE insert_header(...) AS
  BEGIN
    INSERT INTO orders(...) VALUES(...);
    -- ❌ ここでもCOMMIT禁止
  END;
END pkg_order_repo;
/

「下位はCOMMITしない」だけで90%の事故が消えるのが現実です。逆に下位プロシージャでCOMMITが書かれていると、①上位の例外時にROLLBACKしてもコミット済みデータは戻らない、②呼び出し順序が変わると整合性が崩れる、③テスト時に処理途中で確定して再現不可能になる、という3重苦に陥ります。この規約はコードレビューで一律に弾く体制を作るのが効果的です。

例外処理との統合|失敗時のROLLBACKを正しく書く

例外処理とトランザクション制御は常にセットです。失敗時にROLLBACKを書き忘れると未確定の変更が残り、次の処理に悪影響を与えます。正しい統合パターンを4種類に分けて解説します。

パターン1|最上位で全体を保護する

API層やバッチ起動関数で「すべて成功したらCOMMIT、失敗ならROLLBACK→ログ→再raise」のテンプレートを徹底します。これが最も基本かつ重要なパターン。

パターン2|SAVEPOINTで部分復帰

「複数の処理のうち、1部失敗したら他の部分だけ残したい」要件はSAVEPOINTで実現します。ROLLBACK TO sp1でその時点まで戻り、その後COMMITすればSAVEPOINT前の処理は確定されます。詳細はSAVEPOINTを使った部分ロールバックの実装方法を参照してください。

パターン3|AUTONOMOUS_TRANSACTIONで独立コミット

「親トランザクションがROLLBACKされても残したいログ」はPRAGMA AUTONOMOUS_TRANSACTIONで別トランザクションに切り出します。監査ログ・エラー記録などに活用。AUTONOMOUS TRANSACTIONで独立した処理を行う方法で詳述しています。

パターン4|WHEN OTHERSは最終手段

WHEN OTHERSすべての例外を捕まえる包括ハンドラ。使うなら必ずRAISEまたはRAISE_APPLICATION_ERRORで上位へ伝搬してください。握りつぶし(WHEN OTHERS THEN NULL;)は本番障害の最大の温床です。

例外処理とトランザクション制御の統合テンプレ
-- ✅ 標準テンプレート:最上位での例外捕捉+ROLLBACK+ログ+再raise
CREATE OR REPLACE PROCEDURE pkg_api.run_batch AS
BEGIN
  pkg_extract.run;
  pkg_transform.run;
  pkg_load.run;
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;                              -- 全変更を取り消す
    pkg_util_log.error(                    -- 監査ログは独立コミット
      p_module => 'run_batch',
      p_msg    => SQLERRM,
      p_stack  => DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    RAISE;                                 -- 上位(呼び出し元)へ伝搬
END;
/

-- ✅ SAVEPOINT版:複数ジョブの一部失敗を許容
CREATE OR REPLACE PROCEDURE pkg_api.run_multi_jobs AS
BEGIN
  FOR rec IN (SELECT job_id FROM pending_jobs ORDER BY job_id) LOOP
    SAVEPOINT before_each_job;
    BEGIN
      pkg_job_runner.execute(rec.job_id);
    EXCEPTION
      WHEN OTHERS THEN
        ROLLBACK TO before_each_job;       -- このジョブだけ取り消す
        pkg_util_log.error(
          p_module => 'job_' || rec.job_id,
          p_msg    => SQLERRM);
        -- 他のジョブは続行(RAISEしない設計)
    END;
  END LOOP;
  COMMIT;                                  -- 成功したジョブだけ確定
END;
/

-- ❌ アンチパターン:例外を握りつぶす
CREATE OR REPLACE PROCEDURE pkg_bad.run AS
BEGIN
  pkg_x.do_something;
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN NULL;       -- ❌ 致命的:失敗が記録もされず気付けない
END;
/

WHEN OTHERS の握りつぶしは絶対禁止です。①失敗が見えないため数日〜数ヶ月後に「なぜか整合性が崩れている」と判明、②原因調査ログがないので再現不能、③SQLインジェクションの反応差を消してブラインド攻撃を許す、と三重に最悪。「WHEN OTHERSを書くなら必ずRAISEする」をコードレビュー必須項目にしてください。

暗黙コミットの罠|知らずに発火する地雷

「明示的にCOMMITしていないから安全」と思っていても、勝手にコミットされる場面がOracleには複数あります。これが原因で「なぜか部分的に確定している」事故になることがよくあるので、発火条件を必ず把握しておきましょう。

① DDL(CREATE/ALTER/DROP/TRUNCATE)

DDLを実行すると、その前後で自動COMMITが発火します。TRUNCATEもDDLなので注意。たとえば「未コミットのINSERTがある状態でTRUNCATE TABLE temp_table」を実行すると、INSERTも一緒にコミットされてしまいます。バッチ処理中に「途中の一時テーブルをTRUNCATE」している箇所があれば要警戒です。

② セッションの正常終了

SQL*PlusやSQLclでEXITすると、未COMMITの変更が暗黙COMMITされる動作になっています(EXIT ROLLBACKと書けば取消し可能)。デフォルト動作で意図せずコミットされると恐ろしいので、本番運用ではEXIT ROLLBACKを必ず明示するか、スクリプト最後に明示的なCOMMIT/ROLLBACKを書く習慣を付けてください。

③ 接続切断(kill -9・タイムアウト等)

セッションが異常終了した場合はPMONがセッションをクリーンアップする際にROLLBACKされます(暗黙コミットではない)。ただし「意図的にCTRL-Cで止めて再実行」のような場合に前回の途中状態が残るのか戻るのかは挙動が複雑なので、本番では明示的にCOMMIT/ROLLBACKで終わらせるのが安全です。

④ DCL(GRANT/REVOKE)

権限変更系もDDLと同様に暗黙コミットを発火します。PL/SQL内で動的にGRANTする実装は大事故の温床になり得るので、権限変更は別セッション・別ジョブで明確に分離してください。

暗黙コミットの動作確認スクリプト
-- ① DDLで暗黙コミットが発火する例
INSERT INTO orders(order_id, customer_id) VALUES(1, 100);
-- まだCOMMITしていない

CREATE TABLE temp_log(id NUMBER);   -- ← DDLで暗黙COMMIT発火!
DROP TABLE temp_log;                 -- ← もう一度DDLで暗黙COMMIT

ROLLBACK;                            -- もう取り消せない(INSERTは確定済み)
SELECT * FROM orders WHERE order_id = 1;  -- ヒットしてしまう

-- ② 上記事故を回避する方法:DDL前に明示的にコミットor取消し
INSERT INTO orders(order_id, customer_id) VALUES(2, 200);
ROLLBACK;                            -- 先に明示的に判断する
CREATE TABLE temp_log(id NUMBER);   -- これで影響なし

-- ③ TRUNCATE もDDLなので同じ
INSERT INTO logs(log_id, msg) VALUES(1, 'temp');
TRUNCATE TABLE work_table;          -- ← INSERTも一緒にコミット!

EXECUTE IMMEDIATE ‘CREATE TABLE …’のようにPL/SQL内でDDLを動的実行する箇所は、その前後で何が確定されるか必ず確認してください。前段のINSERT/UPDATEが意図せず確定して、失敗時にROLLBACKしても戻らない事故が発生します。DDLを動的実行するパッケージは「呼び出し直前のCOMMIT」を必須にする規約が安全です。

Java/JDBC/APサーバ連携時の注意点

WebアプリやAPIサーバからJDBCで呼ぶPL/SQLでは、JDBCドライバが独自にトランザクション境界を制御しているため、PL/SQL側でCOMMITしてしまうと二重制御で壊れます。Spring/JTA/.NET等のフレームワーク側のトランザクション管理機能と正しく連携するためのルールを整理します。

原則|PL/SQL側ではCOMMITしない

JDBCはデフォルトでautoCommit=falseに設定して使うのが定石で、クライアント側でconnection.commit()またはconnection.rollback()を呼ぶ前提です。PL/SQL側で勝手にCOMMITすると、クライアントの「もう少し処理を続けてからまとめてコミット」という意図と衝突して整合性が壊れます。

SpringのTransactional設計との整合

@Transactionalで囲まれたサービスメソッドからPL/SQLを呼ぶ場合、トランザクション境界はSpring側にあります。PL/SQLは「処理を実行するだけ」「例外で失敗を伝える」役割に徹し、トランザクション制御を一切しない設計が標準です。

失敗の伝え方|RAISEで例外を返す

処理中に異常が起きたらRAISE_APPLICATION_ERROR(-20001, '...')でJDBC例外として返します。クライアント側でJDBC例外を受けてrollbackするのが王道。PL/SQL側でROLLBACKしてOKだけ返してしまうと、クライアントは成功と誤解しデータ不整合に直結します。

Java連携を意識したPL/SQL設計
-- ✅ Java/JDBCから呼ばれる前提:COMMITしない、例外で失敗を伝える
CREATE OR REPLACE PROCEDURE create_order_for_api(
  p_customer_id IN  NUMBER,
  p_items_json  IN  CLOB,
  p_order_id    OUT NUMBER
) AS
BEGIN
  -- 入力検証
  IF p_customer_id IS NULL THEN
    RAISE_APPLICATION_ERROR(-20001, 'customer_id is required');
  END IF;

  -- 業務処理(DML実行)
  pkg_order_service.create_order(p_customer_id, p_items_json, p_order_id);

  -- ❌ ここにCOMMITを書かない(クライアントが管理する)
  -- ❌ ROLLBACKも書かない(クライアントが判断する)

EXCEPTION
  WHEN pkg_order_service.e_invalid_customer THEN
    RAISE_APPLICATION_ERROR(-20002, 'Invalid customer: ' || p_customer_id);
  -- WHEN OTHERS は書かない(自動的に上位へ伝搬される)
END;
/

-- Java側の典型実装(参考)
-- try {
--   conn.setAutoCommit(false);
--   try (CallableStatement cs = conn.prepareCall("{CALL create_order_for_api(?, ?, ?)}")) {
--     cs.setLong(1, customerId);
--     cs.setClob(2, itemsJson);
--     cs.registerOutParameter(3, Types.NUMERIC);
--     cs.execute();
--     orderId = cs.getLong(3);
--   }
--   conn.commit();          // ← ここでコミット
-- } catch (SQLException e) {
--   conn.rollback();        // ← ここでロールバック
--   throw e;
-- }

大量バッチでのコミット頻度|I/OとUNDOのバランス

大量データ処理でのコミット頻度は①頻繁すぎるとI/O律速で遅い、②少なすぎるとUNDO圧迫+ORA-01555リスク、というジレンマがあります。実務的な落としどころは1000〜10000件単位のバッチコミットです。

バッチサイズコミットの実装
DECLARE
  CURSOR cur IS SELECT * FROM input_data;
  TYPE t_rec IS TABLE OF input_data%ROWTYPE;
  v_batch t_rec;
  C_BATCH_SIZE CONSTANT PLS_INTEGER := 1000;  -- 1000件ごとにCOMMIT
BEGIN
  OPEN cur;
  LOOP
    FETCH cur BULK COLLECT INTO v_batch LIMIT C_BATCH_SIZE;
    EXIT WHEN v_batch.COUNT = 0;

    FORALL i IN 1 .. v_batch.COUNT
      INSERT INTO target_table VALUES v_batch(i);

    COMMIT;   -- バッチごとにコミット(ORA-01555を回避)
  END LOOP;
  CLOSE cur;
END;
/

コミット頻度の細かいチューニングは別記事大量データの一括処理におけるコミット頻度とUNDO最適化で詳述しています。本記事のバッチサイズ1000は「迷ったらこれ」の実用値で、行サイズ・並列度・UNDOサイズに応じて100〜10000の範囲で調整してください。

本番で踏むアンチパターン7選

① 下位プロシージャで勝手にCOMMIT

Repository層やUtil層でCOMMITが書かれていると、上位の例外時にROLLBACKしてもコミット済みデータは戻りません。「最上位だけがCOMMITする」規約を徹底し、コードレビューで弾いてください。

② WHEN OTHERSで例外を握りつぶす

WHEN OTHERS THEN NULL;は失敗を完全に隠蔽し、原因不明のデータ破損を量産します。必ずROLLBACK→ログ→RAISEのセットで処理してください。

③ ループ内で1件ずつCOMMIT

10万件INSERTで毎件COMMITするとI/O律速で激遅。バッチサイズ1000〜10000ごとにまとめてコミットしてください。OLTPでは1トランザクション1業務単位が基本。

④ 巨大バッチを1回のCOMMITで処理

1億件を1コミットでやると、UNDOが膨れてORA-01555(スナップショットが古すぎる)が発生し、途中で失敗すると全部やり直し。逆方向のアンチパターンとして「大きすぎるトランザクション」も避けてください。

⑤ DDLの前に未コミットDMLを残す

DDLは暗黙COMMITを発火するため、DDL前のDMLが意図せず確定する事故が起きます。PL/SQL内で動的DDLを実行する場合は、直前で明示的にCOMMITまたはROLLBACKして状態を確定させてください。

⑥ Java連携時にPL/SQL側でCOMMIT

JDBC側のトランザクション境界と二重制御で破綻します。クライアント呼び出し前提のプロシージャでは絶対にCOMMITを書かない規約に統一してください。

⑦ ROLLBACK→続きの処理

ROLLBACK後にそのまま処理を続けると、カーソル状態・パッケージ状態・SAVEPOINTの整合性が崩れる場合があります。ROLLBACKしたら原則として例外を上位へ伝搬させて処理を終わらせる設計が安全です。

実装テンプレート集|コピペで使える3パターン

実務で頻出する3パターンをそのまま使えるテンプレートにまとめました。プロジェクトで標準化して全員で同じスタイルに揃えましょう。

実装テンプレ3種
-- ===========================================================
-- テンプレ1:APIエンドポイント(最上位+例外+ログ+RAISE)
-- ===========================================================
CREATE OR REPLACE PROCEDURE api_xxx(
  p_input  IN  CLOB,
  p_output OUT CLOB
) AS
BEGIN
  -- ビジネスロジックは下位レイヤに完全委譲
  pkg_xxx_service.do_something(p_input, p_output);
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    pkg_util_log.error('api_xxx', SQLERRM,
      DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    RAISE;
END;
/

-- ===========================================================
-- テンプレ2:バッチ処理(バッチサイズコミット)
-- ===========================================================
CREATE OR REPLACE PROCEDURE batch_xxx AS
  CURSOR cur IS SELECT * FROM source_t WHERE processed = 0;
  TYPE t_rec IS TABLE OF source_t%ROWTYPE;
  v_buf  t_rec;
  C_BATCH CONSTANT PLS_INTEGER := 1000;
  v_total PLS_INTEGER := 0;
BEGIN
  OPEN cur;
  LOOP
    FETCH cur BULK COLLECT INTO v_buf LIMIT C_BATCH;
    EXIT WHEN v_buf.COUNT = 0;

    FORALL i IN 1 .. v_buf.COUNT
      UPDATE source_t SET processed = 1
       WHERE id = v_buf(i).id;

    v_total := v_total + v_buf.COUNT;
    COMMIT;   -- バッチごとに確定
  END LOOP;
  CLOSE cur;

  pkg_util_log.info('batch_xxx', '処理件数: ' || v_total);
EXCEPTION
  WHEN OTHERS THEN
    -- ROLLBACKは最終バッチ分のみ取消(既COMMIT分は残る)
    ROLLBACK;
    pkg_util_log.error('batch_xxx', SQLERRM);
    RAISE;
END;
/

-- ===========================================================
-- テンプレ3:複数ジョブの部分コミット(SAVEPOINT)
-- ===========================================================
CREATE OR REPLACE PROCEDURE multi_jobs AS
  v_ok PLS_INTEGER := 0;
  v_ng PLS_INTEGER := 0;
BEGIN
  FOR rec IN (SELECT job_id FROM pending_jobs) LOOP
    SAVEPOINT sp_each;
    BEGIN
      pkg_runner.execute(rec.job_id);
      v_ok := v_ok + 1;
    EXCEPTION
      WHEN OTHERS THEN
        ROLLBACK TO sp_each;
        v_ng := v_ng + 1;
        pkg_util_log.error('job_' || rec.job_id, SQLERRM);
    END;
  END LOOP;

  COMMIT;   -- 成功したジョブをまとめて確定
  pkg_util_log.info('multi_jobs', 'OK=' || v_ok || ' NG=' || v_ng);
END;
/

よくある質問

QPL/SQLでBEGIN/COMMITは省略してもよい?
AOracleはBEGIN文を持ちません。最初のDML実行時に自動的にトランザクションが開始されます。COMMITは省略可能ですが、明示的に書くのが推奨。省略すると「セッション終了時の暗黙コミット」に依存することになり、途中で接続が切れた場合などに挙動が予測しにくくなります。
Q複数のSQLを一気にCOMMITとROLLBACKしたい
A一連のDML文の後ろに1回COMMITまたはROLLBACKを書けばOKです。Oracleでは前回COMMIT以降のすべてのDMLが1つのトランザクションとして扱われるので、まとめて確定/取消しできます。範囲を明示的に区切りたい場合はSAVEPOINTを使えば部分的なロールバックも可能です。
QCOMMIT後にROLLBACKできますか?
Aできません。COMMITしたら以降のROLLBACKでは戻せません。どうしても戻したい場合はFLASHBACK QUERY(過去の時点のデータを取得)かFLASHBACK TABLE(テーブルを過去の時点に戻す)を使います。本番運用では「COMMITは引き返せない一線」と認識して慎重にコミット境界を設計してください。
Qプロシージャ内でCOMMITを書かなかったらいつコミットされる?
A呼び出し元(別のプロシージャ・APIサーバ・SQL*Plus等)がCOMMITするまで未確定のままです。SQL*Plusで対話的に呼んでいるならEXIT時に暗黙コミットされます(事故源なのでEXIT ROLLBACKを明示推奨)。JDBCならクライアント側のautoCommit設定とtransaction管理に従います。
QPRAGMA AUTONOMOUS_TRANSACTIONを使えばCOMMITは独立する?
Aはい、独立した別トランザクションになります。PRAGMA AUTONOMOUS_TRANSACTIONを宣言した関数内のCOMMITは親トランザクションに影響を与えず独立して確定します。監査ログ・エラーログなど「親がROLLBACKされても残したい記録」に最適です。詳細はAUTONOMOUS TRANSACTIONで独立した処理を行う方法を参照してください。
QSELECT文でロックを取りたい場合は?
ASELECT ... FOR UPDATEを使うと取得した行に排他ロックがかかり、COMMIT/ROLLBACKまで保持されます。NOWAIT(待たずに即エラー)/WAIT n(n秒待つ)/SKIP LOCKED(ロック行を飛ばす)の3オプションがあり、キュー処理ではSKIP LOCKEDが定番です。
Qデッドロックが発生したらどうなる?
A片方のセッションでORA-00060が発生しそのトランザクションが自動でROLLBACKされます(強制ROLLBACKされるのはデッドロックを検出した側のみ・全体ではありません)。OracleのデッドロックはMutexやファイルロックと違い必ず検出されるため、プログラム側はORA-00060を捕捉して再試行する設計を入れておけば対処可能です。ロック取得順序を全プロシージャで統一すれば発生確率も激減します。
QCOMMIT WAIT/COMMIT NOWAIT の違いは?
ACOMMIT WAITはREDOバッファのディスク書き込みが完了するまで待つ(デフォルトで安全)。COMMIT NOWAITは書き込みを待たず即返る(性能優先・サーバクラッシュで直前の数件を失う可能性)。通常業務はWAITで運用、「失っても再生成可能なログ書き込みのみ」のような用途でNOWAITを使う場面はありますが、デフォルト動作で十分な場合がほとんどです。
Qバッチで1万件処理中に9999件目で失敗、どう設計する?
A選択肢は3つ。①全件再実行可能な「冪等」設計にする(推奨)、②SAVEPOINTで小分けし失敗箇所だけROLLBACK→続行、③進捗テーブルに「処理済みID」を記録し再実行時にスキップ。①が最もシンプルで現実的。バッチ処理の冪等性確保はトランザクション設計の王道で、リトライ可能性を最初から組み込むと運用がぐっと楽になります。
Qコードレビューで「COMMITしすぎ」と指摘された
A正しい指摘です。下位プロシージャでCOMMITが書かれている、関数内に複数のCOMMITが散在している、失敗時のROLLBACKが書かれていない、のどれかがありそうです。本記事の「層別コーディング規約」セクションに沿って「COMMITは最上位だけ」「ROLLBACKは例外時のみ」で再構成してください。コードレビューで弾く規約として明文化するとチーム全体が改善します。

関連記事で深掘りする

トランザクション制御に関連する記事を集めました。

まとめ|「最上位だけCOMMIT」のシンプルなルールから始める

PL/SQLのCOMMIT/ROLLBACKは、構文を覚えるよりも「誰がいつ実行するか」のトランザクション境界設計が本質です。層別コーディング規約・例外処理との統合・暗黙コミットの理解の3つを押さえれば、本番障害の大半は構造的に防げます。本記事の要点を7つに集約します。

  1. 下位プロシージャはCOMMITしない。最上位の窓口だけが責任を持つ
  2. 例外時はROLLBACK→ログ→RAISEのセットで上位へ伝搬する
  3. WHEN OTHERSで例外を握りつぶさない。沈黙する例外は本番障害の温床
  4. DDL/TRUNCATEは暗黙コミットを発火するので前後のDML状態に注意
  5. Java/APサーバから呼ばれるプロシージャはPL/SQL側で一切コミットしない
  6. 大量バッチは1000〜10000件のバッチサイズコミットで安定運用
  7. SAVEPOINTで部分制御、AUTONOMOUS_TRANSACTIONで独立コミット

既存システムでこれらが守られていない場合は、まず「下位プロシージャのCOMMIT文を撲滅する」ことから始めてください。これだけで「失敗時にROLLBACKが効かない」事故が大半消えます。本記事の実装テンプレートを社内コーディング規約に取り込み、コードレビューで一律に弾く体制を作るのが効果的です。