この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:5
ページ更新者:guest
更新日時:2026-06-11 07:29:05

タイトル: トランザクション制御 (SQL)
SEOタイトル: SQL トランザクション制御 完全ガイド(BEGIN/COMMIT/ROLLBACK/SAVEPOINT/ACID/分離レベル)

この記事の要点
  • トランザクションは "不可分な処理の単位"。途中で失敗したら全部巻き戻す
  • 基本三文: BEGIN / COMMIT / ROLLBACK。部分巻き戻しは SAVEPOINT
  • ACID 特性: 原子性 / 一貫性 / 独立性 / 永続性
  • 分離レベル: READ UNCOMMITTED < READ COMMITTED < REPEATABLE READ < SERIALIZABLE
  • Spring は @Transactional、Laravel は DB::transaction() で宣言的に

トランザクションとは

トランザクションは「論理的に1つにまとまった複数のSQL文の集合」で、全部成功するか、全部巻き戻すかのどちらかしか起こりません。代表例は銀行振込: 口座A から減算する UPDATE と、口座B に加算する UPDATE は片方だけ成功してはいけません。

基本構文(BEGIN / COMMIT / ROLLBACK)

-- 標準SQL / PostgreSQL
BEGIN;                          -- または START TRANSACTION
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;                         -- 確定(永続化)

-- 失敗時
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
-- エラー or 業務ロジックで NG 判明
ROLLBACK;                       -- 巻き戻し

-- MySQL は START TRANSACTION も BEGIN も同等
START TRANSACTION;
-- ...
COMMIT;

-- SQL Server
BEGIN TRANSACTION;
-- ...
COMMIT TRANSACTION;             -- or ROLLBACK TRANSACTION

-- Oracle は明示的な BEGIN 不要(DML 開始時に自動で開始)
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
COMMIT;                         -- or ROLLBACK

ACID 特性

特性意味保証する仕組み
Atomicity(原子性)全部実行 or 全部巻き戻しUndo ログ / Rollback Segment
Consistency(一貫性)制約違反を起こさない状態へ遷移制約検査 / トリガ
Isolation(独立性)同時実行しても直列実行と同じ結果ロック / MVCC
Durability(永続性)COMMIT 後は障害でも残るREDO ログ / WAL

SAVEPOINT で部分ロールバック

BEGIN;
INSERT INTO orders(id, user_id) VALUES (1, 100);

SAVEPOINT sp1;                  -- セーブポイント設定
INSERT INTO order_items(order_id, sku) VALUES (1, 'A');
INSERT INTO order_items(order_id, sku) VALUES (1, 'B');
-- B の挿入に問題発覚

ROLLBACK TO SAVEPOINT sp1;      -- sp1 以降だけ巻き戻し
-- INSERT INTO orders は残っている

INSERT INTO order_items(order_id, sku) VALUES (1, 'C');
RELEASE SAVEPOINT sp1;          -- 使い終わったら解放
COMMIT;

分離レベル (Isolation Level)

同時実行時に「他のトランザクションの未確定変更がどこまで見えるか」を決めます。

レベルDirty ReadNon-RepeatablePhantom性能
READ UNCOMMITTED発生発生発生最速
READ COMMITTED (PG/Oracle 既定)防ぐ発生発生速い
REPEATABLE READ (MySQL 既定)防ぐ防ぐ発生普通
SERIALIZABLE防ぐ防ぐ防ぐ最遅

※ MySQL InnoDB の REPEATABLE READ は Gap Lock で Phantom も実質防ぐ。

-- 分離レベルの設定
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
-- ...
COMMIT;

-- セッション単位
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 現在の分離レベル確認 (MySQL)
SELECT @@transaction_isolation;

-- (PostgreSQL)
SHOW transaction_isolation;

ロックの種類

ロック意味取り方
Shared Lock (S)読み取り共有。複数同時に取れるSELECT ... LOCK IN SHARE MODE / FOR SHARE
Exclusive Lock (X)書き込み排他。他は読み書き不可SELECT ... FOR UPDATE / UPDATE/DELETE
Intention Lock行ロック取得意図のテーブルロックInnoDB が自動付与
Gap Lockインデックス範囲の隙間ロック (MySQL)REPEATABLE READ で自動

デッドロック

2つのトランザクションがお互いのロックを待ち続けて進めない状態。DBが検知して片方を強制ロールバックします。

-- トランザクション A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;  -- ロック A
-- ...
UPDATE accounts SET balance = balance + 100 WHERE id = 2;  -- B のロック待ち

-- トランザクション B (同時実行)
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;   -- ロック B
-- ...
UPDATE accounts SET balance = balance + 50 WHERE id = 1;   -- A のロック待ち
-- → デッドロック検知 → どちらかが ERROR 1213 で ROLLBACK

-- 回避策: ロック取得順を統一する(id 昇順で UPDATE)

Autocommit

多くのDBは Autocommit 既定 ON。1つのSQL文ごとに即 COMMIT されます。

-- MySQL: 確認と切替
SELECT @@autocommit;            -- 1=ON
SET autocommit = 0;             -- OFF。明示的に COMMIT/ROLLBACK 必要

-- PostgreSQL の psql は対話モードでも文ごとに自動 COMMIT
-- BEGIN ... COMMIT で囲めばトランザクション

アプリ側での書き方

Spring (Java) - @Transactional

@Service
public class TransferService {

    @Transactional(
        isolation = Isolation.READ_COMMITTED,
        propagation = Propagation.REQUIRED,
        rollbackFor = Exception.class,
        timeout = 30
    )
    public void transfer(long from, long to, BigDecimal amount) {
        accountRepo.withdraw(from, amount);
        accountRepo.deposit(to, amount);
        // 例外が出れば自動 ROLLBACK、正常終了で COMMIT
    }
}

Laravel (PHP) - DB::transaction

use Illuminate\Support\Facades\DB;

// クロージャ方式(推奨): 例外で自動ロールバック
DB::transaction(function () use ($from, $to, $amount) {
    DB::table('accounts')->where('id', $from)->decrement('balance', $amount);
    DB::table('accounts')->where('id', $to)->increment('balance', $amount);
}, 3); // デッドロック時 3 回まで自動リトライ

// 手動方式
DB::beginTransaction();
try {
    // ...
    DB::commit();
} catch (\Throwable $e) {
    DB::rollBack();
    throw $e;
}

FAQ

Q: DDL(CREATE TABLE 等)はトランザクションに入る?
A: MySQL/Oracle は DDL で暗黙 COMMIT。PostgreSQL は DDL もトランザクションに含められます。

Q: 分離レベルはどれを選ぶべき?
A: 一般的な業務系は READ COMMITTED で十分。集計や厳密整合性が必要なら REPEATABLE READ / SERIALIZABLE。

Q: COMMIT し忘れたまま接続切断したらどうなる?
A: 自動的に ROLLBACK されます。COMMIT されない変更は他から見えません。