トランザクション分離レベルとは?PostgreSQLとMySQLで既定値が違う
データベースの「トランザクション分離レベル」を、プロジェクトのどこかで明示的に設定した記憶はあるでしょうか。設定した記憶がない、という開発者は少なくないはずです。
ここで起きている誤解は、「設定していないから、まだ決まっていない」というものです。実際には違います。設定していなければ、製品が用意した既定値が適用されています。そしてその既定値は、PostgreSQLとMySQLで異なります。在庫の引当、予約枠の確保、決済の確定——二重処理が業務の損害になる機能は、誰も選んだ覚えのない値の上で動いていることになります。
この記事では、両製品の公式ドキュメントが「既定はどれか」「そのレベルで何を防がないか」をどう書いているかを確認し、レベルを変える前に決めておくことを整理します。
30秒で要点
- 分離レベルは、設定しなければ製品の既定値が適用される。「未設定」という状態は存在しない
- PostgreSQL公式ドキュメントは既定をRead Committedと記載し、MySQL公式ドキュメントはInnoDBの既定をREPEATABLE READと記載している(2026年9月30日時点のcurrent/8.4マニュアルで確認)
- PostgreSQLの分離レベル表では、Read Committedでもnonrepeatable read・phantom read・serialization anomalyは「Possible」。読むべきは「何を防ぐか」より「何を防がないと公式が書いているか」
- レベルを上げると、直列化の失敗時にトランザクション全体を再実行する義務がアプリケーション側に移る(SQLSTATE '40001')
- 性能への影響はワークロード依存で、公式ドキュメントに数値はない
なぜ「既定値が整合性を決めている」と気づきにくいのか
分離レベルが意識から外れやすい理由は、開発の進み方そのものにあります。
開発初期、データベースにアクセスしているのはたいてい自分ひとりです。同時に同じ行を触る処理が存在しないため、分離レベルがどれであっても結果は変わりません。テストも通ります。レビューでも話題になりません。分離レベルは、同時実行が起きて初めて挙動として表に出る設定だからです。
さらに、ORM(プログラムの言語とデータベースのやりとりを仲介するライブラリ)やフレームワークがトランザクションの開始・終了を自動で扱うため、BEGIN を自分で書く機会すらないことがあります。自分で書いていないものは、自分が選んだという感覚になりません。
その結果、「分離レベルは、必要になったら選ぶもの」という認識が残ります。しかし実際には、必要になる前から既に1つが選ばれています。選んだのは自分ではなく、製品のインストール時の既定値です。
既定値は製品ごとに違う——Read Committed と REPEATABLE READ
ここが本記事の中心です。両製品の公式ドキュメントは、既定値をそれぞれ明記しています。
PostgreSQL:公式ドキュメントのTransaction Isolationの章は、Read Committed が PostgreSQL における既定の分離レベルであると記載しています。
MySQL(InnoDB):公式ドキュメントのInnoDBの分離レベルの節は、InnoDB が SQL:1992 標準の記述する4つの分離レベル(READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLE)をすべて提供するとしたうえで、InnoDB の既定の分離レベルは REPEATABLE READ であると記載しています。REPEATABLE READ の節にも「これが InnoDB の既定の分離レベルである」と書かれています。
つまり、同じアプリケーションコードを PostgreSQL から MySQL へ(あるいはその逆へ)移した時点で、コードを1行も変えていないのに、同時実行時の見え方の前提が変わります。データベース移行やマネージドサービスの切り替えを検討しているなら、移行対象の一覧に「現在の分離レベル」という行を足す価値があります。
確かなこと:両製品の既定値が異なることは、それぞれの公式ドキュメントの記載から直接確認できます(2026年9月30日時点のcurrentマニュアルおよび8.4リファレンスマニュアル)。
まだ確かではないこと:マネージドサービス(クラウド事業者が運用を代行するデータベース)の既定値がこれと同じかどうかは、本記事が参照した2つの公式ドキュメントからは分かりません。パラメータグループなどで事業者側が値を変えている可能性があるため、自分の環境で実際に確認する必要があります。どちらの製品の既定値が「安全」かという優劣も、どちらの公式ドキュメントも述べていません。
読むべきは「何を防ぐか」ではなく「何を防がないと書かれているか」
分離レベルの解説は「上のレベルほど厳密」という説明になりがちですが、実務で効くのは逆向きの読み方です。今いるレベルについて、公式ドキュメントが何を「起こりうる」と書いているかを見ます。
PostgreSQL公式ドキュメントの分離レベル表(Table 13.1)は、Read Committed の行を次のように記載しています。
| 現象 | Read Committed での記載 |
|---|---|
| dirty read(他トランザクションの未確定の変更が見えてしまう) | Not possible |
| nonrepeatable read(同じ行を2回読むと値が変わっている) | Possible |
| phantom read(同じ条件で2回検索すると行数が変わっている) | Possible |
| serialization anomaly(個々は成功したのに、どの順番で1本ずつ実行しても再現しない結果になる) | Possible |
この表で注意すべきは、PostgreSQLにおける実際の挙動を示した表であり、「SQL標準が許すかどうか」の表ではないことです。同じ表の Read Uncommitted と Repeatable Read の欄には「Allowed, but not in PG」(標準では許容されるが、PostgreSQLでは起きない)という表記があります。標準の話と製品の話を混ぜて読むと、ここで取り違えが起きます。
MySQL側でも同じ読み方ができます。MySQL公式ドキュメントは、READ COMMITTED ではギャップロック(インデックス上の値と値の「すきま」に対するロック)が無効になるため、ロック対象のレコードのすぐ隣に他セッションが自由に新しい行を挿入でき、ファントム行の問題が起こりうると記載しています。ギャップロックが使われるのは外部キー制約のチェックと重複キーのチェックに限られる、とも書かれています。
言い換えれば、どのレベルを選んでも「防がないもの」のリストが付いてきます。選ぶべきなのは「最も厳密なレベル」ではなく、「自分の業務で損害になる現象が、そのリストに入っていないレベル」です。
レベルを上げた側が引き受ける義務
「それなら厳しいほうに寄せればいい」と考えたくなりますが、上げた側には新しく負うものがあります。
PostgreSQL公式ドキュメントは、直列化の失敗(複数のトランザクションを同時に実行した結果、1本ずつ順番に実行したのと同じ結果にならないと判断され、エラーになること)が起きたときの対処として、アプリケーションは現在のトランザクションを中断し、トランザクション全体を最初からやり直すべきだと記載しています。この種のエラーのSQLSTATE(SQLの標準エラーコード)は '40001' です。なお、この指示が置かれているのは Repeatable Read の説明の文脈です。
これは「リトライを実装してください」という以上の意味を持ちます。トランザクション全体の再実行とは、途中で発行したメール送信や外部システムへの通知といった、データベースの外に出てしまった処理まで含めてやり直せる構造になっているか、という問いです。トランザクションの中で外部システムを呼んでいる設計は、ここで破綻します。この点は、API(システム連携の窓口)の再送可能性を扱った冪等性とは?「もう一度送っていい処理か」を決めていないAPIの弱点と地続きです。
逆方向——レベルを下げる判断についても、公式ドキュメントは期待を過度に持たせない書き方をしています。MySQL公式ドキュメントは READ COMMITTED について、UPDATE と DELETE は更新・削除する行だけをロックし、条件に合わなかった行のレコードロックは WHERE 条件の評価後に解放されると説明したうえで、これによりデッドロック(互いに相手のロック解放を待って進めなくなる状態)の確率は大きく下がるが、それでも起こりうると明記しています。
「下げればデッドロックが消える」ではなく「確率が下がる」。この書き分けを読み落とすと、デッドロック対応のコードを消してしまう判断につながります。
判断軸:レベルの比較ではなく、1つの処理を紙の上で追う
分離レベルの表を見比べて「どれにするか」を決めようとすると、抽象的すぎて決まりません。順序を逆にします。
- 二重処理されたら業務の損害になる処理を1つだけ選ぶ(在庫の引当、予約枠の確保、決済の確定など)
- 現在の分離レベルを実際に確認する。「たぶん既定値」ではなく、稼働している環境で問い合わせて確かめる
- その処理が、そのレベルで公式が「Possible」としている現象に当たるかを紙の上で追う。「在庫数を読んで、引けるか判断して、更新する」という流れなら、読んだ時点と更新する時点のあいだに他のトランザクションが入りうるか
- 当たるなら、対処の選択肢を並べる。レベルを上げる/明示的な行ロックを取る/一意制約で二重を弾く、など。レベルを上げる場合は、前節の再実行の義務をセットで引き受ける
この順序なら、「厳密なほうが安心」という感覚ではなく、守る対象から逆算した選択になります。
なお、効果を測る場合は分母と分子を先に決めてください。たとえば「直列化失敗の発生率」なら、分母は対象となるトランザクションの実行件数、分子はSQLSTATE '40001' で中断された件数、単位は件/日です。この定義を決めずに「エラーが増えた気がする」で判断すると、レベル変更の影響とアクセス増加の影響が区別できなくなります。
自分でも試せる、最小の検証
データベースのクライアントを2つ開き、どちらでもトランザクションを開始してください。片方で在庫テーブルの特定の行を読み、そのまま確定せずに置いておきます。もう片方で同じ行を更新して確定します。そのうえで、最初のクライアントでもう一度同じ行を読んでみてください。
値が変わって見えるか、変わらずに見えるか。これが、いま自分が立っている分離レベルの実像です。コードを書く前に、この1往復を確認するだけで、「2回読んだら同じ値である」と暗黙に仮定していた箇所に見当がつきます。
判断の土台として押さえておくこと
- 分離レベルに「未設定」はない。設定していなければ製品の既定値が適用されている
- PostgreSQLの既定はRead Committed、MySQL InnoDBの既定はREPEATABLE READ。移行・切り替えは、この前提が変わる瞬間である
- 公式ドキュメントで読むべきは「防ぐもの」より「Possible と書かれているもの」
- レベルを上げると、直列化失敗時にトランザクション全体を再実行する義務が付いてくる。レベルを下げてもデッドロックは消えない(確率が下がるだけ)
- 性能への影響は参照した公式ドキュメントに数値がなく、ワークロード依存。自分の環境で測るしかない
分離レベルに限らず、「誰も選んだ覚えのない既定値」がシステムの前提を決めている箇所は、ほかにもあります。自社のどこにそれがあるかを洗い出すところから整理したい場合は、状況を言語化するところから始めることができます。
状況を整理する(15分)より深く学ぶ
- 冪等性とは?「もう一度送っていい処理か」を決めていないAPIの弱点:二重実行が業務損害になる処理の、再送可能性の設計
- API設計のベストプラクティス:RESTful、GraphQL、gRPCの選定基準と実践:連携の窓口そのものの設計判断
参考資料・引用元
- PostgreSQL Global Development Group, "Transaction Isolation" (PostgreSQL Documentation, current). https://www.postgresql.org/docs/current/transaction-iso.html (2026年9月30日確認)
- Oracle Corporation, "Transaction Isolation Levels" (MySQL 8.4 Reference Manual). https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html (2026年9月30日確認)