ACIDだけですべての業務規則が守られるわけではない

ACIDは原子性・一貫性・分離性・耐久性を表します。一貫性はデータベースが業務規則を自動で推論するという意味ではなく、制約やトランザクション設計で規則を表す必要があります。また、分離レベルの名称が同じでも、エンジンごとに動作は異なります。

ダーティリードは未コミット値を読むこと、反復不能読み取りは同じ行を読み直したとき値が変わること、ファントムは条件に合う行集合が変わることです。さらに、各トランザクションがコミットしても、直列実行のどの順序でも説明できない結果(直列化異常)が残る場合があります。

エンジンごとの読み取り規則を比較する

設定・エンジン通常読み取りのスナップショット注意点確認すること
PostgreSQL Read Committed(既定)各statementの開始時点同じトランザクション内のSELECTが異なるコミット状態を見ることがある複数文の確認を一つの安定したスナップショットとみなさない
PostgreSQL Repeatable Read最初の通常statementで取得したトランザクション単位のsnapshotdirty/non-repeatable/phantomは防ぐが直列化異常は残り、競合更新で中断されることがあるwrite skewとトランザクション全体の再試行を確認
PostgreSQL Serializable直列実行と同等になるよう競合を監視待機だけでなく直列化エラーで失敗することがあるSQLSTATE 40001時に全トランザクションを再試行できるか
MySQL InnoDB Repeatable Read(既定)通常のconsistent readは最初の読み取りで作られたsnapshotロック読み取りは別の状態を見得る。条件と索引によってnext-key/gap lockが挿入を止めるSELECT ... FOR UPDATE/SHAREと走査する索引範囲を確認

PostgreSQLでRead Uncommittedを指定してもRead Committedと同じように動きます。PostgreSQL Repeatable Readは標準の最低要件より強くphantomを防ぎますが、すべての直列化異常までは防ぎません。InnoDBの通常SELECTはsnapshot読み取りであり、ロック読み取りやUPDATE/DELETEは別のロック規則に従います。「読み取りは書き込みを決して妨げない」と一般化しないでください。

仮想例:当直者二人が同時に勤務を外れる

当直者が二人いて、最低一人は勤務を続ける必要があるとします。トランザクションAとBがそれぞれ勤務者二人を確認し、別々の行を一つずつ無効化します。snapshot型のRepeatable Readでは別の行を変更するため両方がコミットし、勤務者がゼロになるwrite skewが起こり得ます。これは可能性を示す例であり、すべてのエンジン・設定で同じ結果になるという意味ではありません。

不変条件を守るには、実際のエンジンと版でSerializableとトランザクション全体の再試行を検証するか、共通のguard rowや制約の設計を検討します。各処理が一行だけ更新するから安全だとは推論できません。

実務での確認順

  1. 製品、ストレージエンジン、版、設定された分離レベルを記録します。
  2. 通常読み取りかロック読み取りか、snapshotがstatement単位かtransaction単位かを調べます。
  3. 守る業務不変条件を同時実行の順序として表し、lost update・write skew・phantomのどれが問題か整理します。
  4. 制約・ロック・上位分離レベルを選び、deadlockや直列化失敗ではトランザクション全体を再試行します。
  5. テスト環境で同時セッションを再現し、遅延・失敗率・再試行コストを測定します。

例は説明用の仮想ケースで、データベース上で実行していません。実装詳細は以下の版別公式資料を参照してください。

資料:PostgreSQL 18 Transaction Isolation、MySQL 8.4 InnoDB Transaction Isolation、MySQL 8.4 Consistent Nonlocking Reads、MySQL 8.4 Locking Reads。