【Oracle】トランザクション完全ガイド|COMMIT・ROLLBACK・SAVEPOINT・分離レベル・SELECT FOR UPDATE・読み取り一貫性

【Oracle】トランザクション完全ガイド|COMMIT・ROLLBACK・SAVEPOINT・分離レベル・SELECT FOR UPDATE・読み取り一貫性 Oracle

トランザクションは「一連の操作を1つの単位として扱い、すべて成功するか、すべて取り消すか」を保証する仕組みです。Oracle のトランザクションは最初の DML(INSERT/UPDATE/DELETE)文の実行時に自動的に開始され、COMMIT または ROLLBACK で終了します。

この記事でわかること

  • COMMIT・ROLLBACK の基本と暗黙コミット(DDL 実行時)
  • SAVEPOINT による部分ロールバックの実装方法
  • Oracle の読み取り一貫性:SCN と MVCC(マルチバージョン同時実行制御)
  • SET TRANSACTION による分離レベルの指定
  • SELECT FOR UPDATE による行ロックと NOWAIT・WAIT・SKIP LOCKED
  • LOCK TABLE でテーブルレベルのロックをかける方法
  • デッドロック予防のためのロック取得順序の原則
スポンサーリンク

COMMIT・ROLLBACK の基本

COMMIT・ROLLBACK の基本操作
-- トランザクション開始: 最初の DML 実行時に自動開始(明示的な BEGIN は不要)
UPDATE employees SET salary = salary * 1.1 WHERE department_id = 50;
INSERT INTO audit_log VALUES (SYSDATE, 'salary_update', USER);

-- COMMIT: すべての変更を確定(他のセッションから見えるようになる)
COMMIT;

-- ROLLBACK: すべての変更を取り消し(最後の COMMIT 時点に戻す)
UPDATE employees SET salary = 0;  -- 誤操作
ROLLBACK;                          -- すべての変更を取り消し
SELECT salary FROM employees WHERE department_id = 50;  -- 元の値に戻っている
暗黙コミット(DDL 実行時に自動 COMMIT)
Oracle では CREATEDROPALTERTRUNCATE などの DDL 文を実行すると、その前後に自動的に COMMIT が発生します。つまり DDL の前に未コミットの DML があった場合、その DML も一緒にコミットされます。また DDL の後の ROLLBACK は DDL 自体を取り消せません。未コミットの変更がある状態で誤って DDL を実行しないよう注意してください。

SAVEPOINT:部分ロールバック

SAVEPOINT はトランザクション内に「セーブポイント」を設定し、その時点まで部分的にロールバックできます。複数の操作を段階的に実行する PL/SQL 処理でよく使われます。

SAVEPOINT による部分ロールバック
DECLARE
    v_count NUMBER;
BEGIN
    -- Step 1: 古いデータを削除
    DELETE FROM order_items WHERE order_id = 1001;
    SAVEPOINT sp_after_delete;   -- ここにセーブポイントを設定

    -- Step 2: 新しいデータを INSERT
    INSERT INTO order_items VALUES (1001, 'ItemA', 5, 1000);
    INSERT INTO order_items VALUES (1001, 'ItemB', 2, 3000);
    SAVEPOINT sp_after_insert;

    -- Step 3: 合計金額を更新
    UPDATE orders
    SET total_amount = (SELECT SUM(qty * unit_price) FROM order_items WHERE order_id = 1001)
    WHERE order_id = 1001;

    -- もし Step 3 だけ失敗した場合、Step 3 だけをロールバック
    -- ROLLBACK TO sp_after_insert;  -- sp_after_insert 以降の変更だけを取り消す

    COMMIT;  -- すべて成功したら確定
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;  -- 何かエラーが起きたらトランザクション全体をロールバック
        RAISE;
END;
/

-- 注意: ROLLBACK TO savepoint は SAVEPOINT 後の変更だけを取り消す
-- SAVEPOINT 自体は COMMIT するまで有効なまま

Oracle の読み取り一貫性:SCN と MVCC

Oracle は SCN(System Change Number) を使った MVCC(Multi-Version Concurrency Control) により、読み取り操作がほかのセッションの書き込みをブロックしません。

  • SELECT はクエリ開始時点の SCN を記録し、その時点のデータを一貫して読む
  • 別のセッションが同時に UPDATE・INSERT・DELETE してもロックせず影響を受けない
  • 変更前のデータは UNDO 表領域に保持され、古い版として提供される
  • これが Oracle の「読み取りは書き込みをブロックしない」という特性の根拠
フラッシュバック・クエリで過去の状態を参照(SCN の活用)
-- フラッシュバック・クエリ: AS OF TIMESTAMP で過去時点のデータを参照
SELECT * FROM employees
AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '1' HOUR)
WHERE department_id = 50;

-- AS OF SCN でも参照可能(SCN はシステムの変更番号)
SELECT * FROM employees
AS OF SCN 12345678
WHERE department_id = 50;

-- 現在の SCN を確認
SELECT CURRENT_SCN FROM V$DATABASE;

-- 誤ってデータを削除した場合の復元(フラッシュバック・クエリで取得してINSERT)
INSERT INTO employees
SELECT * FROM employees
AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '10' MINUTE)
WHERE employee_id = 999;  -- 削除してしまった行を復元

トランザクション分離レベル

Oracle がデフォルトでサポートする分離レベルは READ COMMITTEDSERIALIZABLE の2種類です。

分離レベル 説明 ダーティリード ファントムリード
READ COMMITTED(デフォルト) 各 SQL 実行時点のコミット済みデータを読む なし あり得る
SERIALIZABLE トランザクション開始時点のスナップショットを読む なし なし
SET TRANSACTION で分離レベルを変更する
-- SERIALIZABLE: トランザクション開始から終了まで同じスナップショットで読む
-- → 途中で他セッションがコミットしても見えない(完全なスナップショット隔離)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN
    SELECT COUNT(*) INTO :v_before FROM orders WHERE status = 'PENDING';

    -- 別セッションが PENDING → COMPLETED に変更してコミットしても...
    -- このトランザクション内では引き続き古い数を見る

    SELECT COUNT(*) INTO :v_after FROM orders WHERE status = 'PENDING';
    -- :v_before = :v_after(SERIALIZABLE なので同じ値が返る)
END;
/
COMMIT;

-- READ COMMITTED に戻す(デフォルト。明示的に設定する場合)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 注意: SET TRANSACTION はトランザクションの最初(最初の DML の前)に実行する必要がある
-- DML の後に SET TRANSACTION を実行すると ORA-01453 エラー

SELECT FOR UPDATE:行ロックの取得

SELECT FOR UPDATE は取得した行に対して排他ロックをかけます。「読んでから更新する」パターンで、読み取りと更新の間に他セッションに行を更新されたくない場合に使います。

SELECT FOR UPDATE・NOWAIT・WAIT・SKIP LOCKED
-- 基本: SELECT FOR UPDATE で行をロック(他セッションの UPDATE は待機させる)
SELECT employee_id, salary
FROM employees
WHERE department_id = 50
FOR UPDATE;   -- このセッションが COMMIT/ROLLBACK するまで他セッションは UPDATE できない

-- NOWAIT: ロックが取れなければ即座に ORA-00054 エラーを返す(待機しない)
SELECT employee_id, salary
FROM employees
WHERE employee_id = 100
FOR UPDATE NOWAIT;   -- ロック中なら ORA-00054: resource busy

-- WAIT n: n秒待機してロックが取れなければエラーを返す
SELECT employee_id, salary
FROM employees
WHERE employee_id = 100
FOR UPDATE WAIT 5;   -- 5秒待機して取れなければエラー

-- SKIP LOCKED: ロック中の行をスキップして残りを取得(キュー処理に有用)
SELECT job_id, payload
FROM job_queue
WHERE status = 'PENDING'
FOR UPDATE SKIP LOCKED
FETCH FIRST 10 ROWS ONLY;   -- ロックされていない先頭10件を取得

-- OF 句: 結合クエリで特定テーブルの行だけロック
SELECT e.employee_id, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
FOR UPDATE OF e.salary;    -- employees の行だけロック(departments はロックしない)
SKIP LOCKED はジョブキューの並列処理に最適
FOR UPDATE SKIP LOCKED を使うと、複数のワーカープロセスが同じキューテーブルから重複なくジョブを取得できます。ロック中(他のワーカーが処理中)の行を自動的にスキップし、処理可能な行だけを効率よく取得できます。Oracle 11g R2 以降で使用可能です。

LOCK TABLE:テーブルレベルのロック

LOCK TABLE でテーブル全体にロックをかける
-- EXCLUSIVE: 他セッションの読み取りも書き込みもブロック(最強のロック)
-- SHARE: 他セッションは読み取りOKだが書き込みをブロック
-- ROW EXCLUSIVE: 通常の DML で自動取得されるロック
LOCK TABLE employees IN EXCLUSIVE MODE;
-- → このセッションが COMMIT/ROLLBACK するまで、他セッションは employees を参照・更新できない

-- NOWAIT でロックできない場合は即エラー
LOCK TABLE employees IN SHARE MODE NOWAIT;

-- 使用例: テーブル全体を一括更新する前に競合を防ぐ
LOCK TABLE orders IN EXCLUSIVE MODE;
UPDATE orders SET status = 'ARCHIVED' WHERE created_at < DATE '2020-01-01';
COMMIT;   -- ロックは自動解放

デッドロック予防:ロック取得順序の原則

デッドロックは複数のセッションが互いに相手のロックを待つ状態です。Oracle は ORA-00060 でデッドロックを自動検出し、一方のトランザクションをロールバックします。

  • 複数テーブルを更新する順序を全セッションで統一する(A→B の順なら常に A→B)
  • トランザクションを短く保つ(長時間ロックを保持しない)
  • FOR UPDATE NOWAIT/WAIT を使い、ロック取得失敗を検出して再試行する
  • 行ロック(SELECT FOR UPDATE)よりテーブルロック(LOCK TABLE)の方がデッドロックを起こしにくい場合がある(細かいロックほど競合する範囲が広がる)

まとめ

  • COMMIT:変更を確定。ROLLBACK:変更を取り消し。DDL は前後で自動 COMMIT
  • SAVEPOINT:部分ロールバックのマーカー。PL/SQL の段階的処理に便利
  • 読み取り一貫性(MVCC):SELECT は書き込みをブロックしない。SCN でスナップショットを管理
  • SERIALIZABLE:トランザクション全体を同じスナップショットで読む。ファントムリードを防ぐ
  • SELECT FOR UPDATE:行ロック取得。NOWAIT・WAIT・SKIP LOCKED で待機動作を制御
  • デッドロック予防:複数テーブルのロック順序を統一し、トランザクションを短く保つ

デッドロック(ORA-00060)の詳細な調査・対処方法はデッドロック(ORA-00060)の原因と解決方法完全ガイドを、フラッシュバック・クエリによる過去データの参照・復元はフラッシュバックの使い方完全ガイドも参照してください。