トランザクション分離レベルとは、「同時に動いている他のトランザクションの変更を、どこまで見えないようにするか」を決める設定です。分離を強くするほどデータの整合性は高まりますが、同時に処理できる量(並行性)は下がります。その間のどこを選ぶかを、4つのレベルで指定します。

この記事では、4つの分離レベルの違いを具体例で説明し、MySQLとPostgreSQLでデフォルトが違うことの影響、そして分離レベルを上げても防げない「ロストアップデート」の対策まで解説します。

まず、同時実行で起きる3つの現象

分離レベルは、次の3つの現象を「防ぐかどうか」で定義されています。

ダーティリード(Dirty Read)

他のトランザクションが、まだコミットしていない変更が見えてしまう現象です。相手がロールバックすると、存在しなかったはずのデータを読んだことになります。

反復不能読み取り(Non-Repeatable Read)

同じトランザクションの中で、同じ行を2回読むと値が変わっている現象です。

BEGIN;
SELECT balance FROM accounts WHERE id = 1;  -- 1000
-- この間に、別のトランザクションが更新してコミット
SELECT balance FROM accounts WHERE id = 1;  -- 800
COMMIT;

ファントムリード(Phantom Read)

同じ条件で2回検索すると、行が増えている(または減っている)現象です。別のトランザクションが条件に合う行をINSERTしてコミットしたために起きます。集計処理の途中で起きると、合計と明細が合わなくなります。

4つの分離レベルの違い

SQL標準では、4つの分離レベルが定義されています。

分離レベル ダーティリード 反復不能読み取り ファントムリード
READ UNCOMMITTED 起きうる 起きうる 起きうる
READ COMMITTED 防ぐ 起きうる 起きうる
REPEATABLE READ 防ぐ 防ぐ 起きうる(※)
SERIALIZABLE 防ぐ 防ぐ 防ぐ

※SQL標準の定義。実際のDBでは、REPEATABLE READでもファントムを防ぐ実装があります(後述)。

  • READ UNCOMMITTED — 最も緩い。未コミットの変更も見える。実務で使うことはほぼありません
  • READ COMMITTED — コミット済みの最新データを読む。多くのDBのデフォルト
  • REPEATABLE READ — トランザクション中に読んだ行は、何度読んでも同じ値
  • SERIALIZABLE — 同時に走ったトランザクションを、1つずつ順番に実行したのと同じ結果に保証する。最も厳格

DBごとのデフォルトの違い

ここが実務で最も効く知識です。主要なDBで、デフォルトの分離レベルが違います。

DB デフォルト
PostgreSQL READ COMMITTED
Oracle Database READ COMMITTED
SQL Server READ COMMITTED
MySQL(InnoDB) REPEATABLE READ

つまり、冒頭の「同じSELECTの結果が変わる」現象は、PostgreSQLのデフォルトでは起きますが、MySQLのデフォルトでは起きません。同じアプリケーションコードでも、DBを移行しただけで挙動が変わることがあるわけです。

同じ名前でも、中身が同じとは限らない

さらに注意が必要なのは、同じ名前の分離レベルでも、DBごとに実装が違うことです。

  • PostgreSQLのREPEATABLE READ — トランザクション開始時のスナップショットを使うため、ファントムも起きません。その代わり、同じ行を同時に更新しようとすると、後から来た側がエラー(シリアライゼーション失敗)になり、リトライが必要です
  • MySQL(InnoDB)のREPEATABLE READ — 通常のSELECTはスナップショットで読み、ロックを伴う読み取り(SELECT ... FOR UPDATE等)ではギャップロックを使ってファントムを抑えます
  • PostgreSQLのREAD UNCOMMITTED — 指定しても、READ COMMITTEDと同じ動作になります
  • Oracle — READ COMMITTEDとSERIALIZABLEの2つ(と読み取り専用)のみ

分離レベルの名前は「最低限の保証のラベル」と捉え、実際の挙動は使っているDBのドキュメントで確認するのが確実です。

分離レベルを確認・変更する

-- 現在の分離レベルを確認
-- MySQL
SELECT @@transaction_isolation;
-- PostgreSQL
SHOW transaction_isolation;

-- このトランザクションだけ変更(PostgreSQL)
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

アプリケーションからは、フレームワークの設定(例:Springの @Transactional(isolation = ...))で指定することもできます。

分離レベルでは防げない「ロストアップデート」

実務で一番被害が大きいのが、この現象です。

2人が同時に「残高を読む→計算する→書き戻す」を行うと、後から書き込んだ方が、先の更新を上書きして消してしまいます。

Aさん: 残高1000を読む → 100引いて900を書き込む
Bさん: 残高1000を読む → 200引いて800を書き込む(Aさんの更新が消える)
結果: 800(正しくは700)

READ COMMITTEDでは防げず、DBや書き方によってはREPEATABLE READでも防げません。アプリケーション側で明示的に守る必要があります。

対策1:悲観ロック(SELECT ... FOR UPDATE)

BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;  -- 行をロック
UPDATE accounts SET balance = 900 WHERE id = 1;
COMMIT;

読んだ時点で行をロックするので、他のトランザクションはコミットまで待たされます。確実ですが、待ちが発生します。

対策2:楽観ロック(バージョン列)

UPDATE accounts
SET balance = 900, version = version + 1
WHERE id = 1 AND version = 5;   -- 読んだときのバージョン

更新件数が0件なら、誰かが先に更新したということなので、読み直してやり直します。競合が少ない処理では、待ちが発生しないぶん効率的です。多くのORMやフレームワークに仕組みが用意されています。

対策3:計算をSQLの中で完結させる

UPDATE accounts SET balance = balance - 100 WHERE id = 1;

「読んでから書く」をやめ、1文で更新すれば、この形のロストアップデートは起きません。

どの分離レベルを選べばいいか

  • 基本はDBのデフォルトのままにし、そのデフォルトが何かをチームで把握しておく
  • 「読んでから書き戻す」処理は、分離レベルに頼らず、悲観ロック・楽観ロック・1文での更新のどれかで守る
  • 複数のSELECTで帳票や集計を作るときは、読み取り専用トランザクション(REPEATABLE READ等)で囲み、同じ時点のデータを見るようにする
  • SERIALIZABLEを使う場合は、失敗時のリトライ処理が必須。リトライなしで上げると、エラーが増えたように見えます

よくある質問

Q. 全部SERIALIZABLEにすれば安全ですか? A. 整合性は最も強くなりますが、両立できないトランザクションはエラーになるため、アプリ側にリトライ処理が必須です。並行性も下がるため、必要な処理に限って使うのが一般的です。

Q. MySQLからPostgreSQLに移行するときの注意点は? A. デフォルトがREPEATABLE READからREAD COMMITTEDに変わります。同じトランザクション内で同じデータを複数回読む処理は、結果が変わらないか確認してください。

Q. トランザクションを長く開いておくと何が問題ですか? A. ロックを持ち続けて他の処理を待たせるほか、DBが古いデータを掃除できなくなり、全体の性能が低下することがあります。トランザクション内で外部APIを呼んだり、ユーザーの入力を待ったりするのは避けましょう。

Q. 読み取りだけの処理にもトランザクションは必要ですか? A. 複数のSELECTの結果を組み合わせる場合は、同じ時点のデータを見るために有効です。

まとめ

  • 分離レベルは、ダーティリード・反復不能読み取り・ファントムリードをどこまで防ぐかで決まる
  • PostgreSQL・Oracle・SQL ServerはREAD COMMITTED、MySQLはREPEATABLE READがデフォルト
  • 同じ名前でもDBごとに実装が違う。挙動はドキュメントで確認する
  • ロストアップデートは分離レベルでは防げない。悲観ロック・楽観ロック・1文での更新で守る

株式会社RJCが携わる保険・金融・決済のシステムでは、「同時に2つの処理が走ったらどうなるか」が設計レビューの定番の論点です。こうした知識を、チームで共有しながら開発しています。

▶ RJC丸わかり https://www.r-jc.jp/guide/?mediacode=02-00001-003

▶ エントリーはこちら(1〜2分で完了します) https://www.r-jc.jp/recruitment/?mediacode=02-00001-003

株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?