1 トランザクションの概念
1.1 定義と目的
トランザクションとは、複数の操作を「一つの処理単位」として扱い、途中結果が外部から見えてもよいか/確定してよいかを制御しながら進める仕組みである。目的は、失敗や障害が発生してもデータや状態が崩れないようにし、成功した処理だけを確定する点にある。
1.2 代表的な利用シーン
1.2.1 データベース処理
データベースでは、顧客情報の更新、注文の作成、支払い履歴の記録など複数の更新をトランザクションとしてまとめる。コミットにより変更を確定し、問題があればロールバックで取り消すことで、整合性の維持を狙う。
1.2.2 分散システムの処理
分散環境では、複数のサービスやノードにまたがる処理を扱うため、単一のデータベース内よりも制御が難しくなる。そこでトランザクションの考え方を応用し、成功・失敗の扱いをサービス間で調停しながら一貫性を確保する。
1.3 トランザクションと「一連の処理」の関係
トランザクションは「一連の処理」をそのまま列挙するだけではなく、境界の設定と結果の確定方式を含む。すなわち、どこまでを同時に成功させる対象とするか、そして確定前に観測される内容をどう扱うかが設計上の中心となる。
2 トランザクションの基本特性
2.1 一貫性を支える性質
トランザクションの性質として、原子性・整合性・分離性・耐久性がよく整理される。これらは「処理のまとまり」と「同時実行下の振る舞い」と「障害時の回復」を説明するための枠組みである。
2.1.1 原子性
原子性は、トランザクション内の一連の更新が「全部実行されるか、なければ全部取り消されるか」という二者択一の性質を意味する。部分的な反映が残らないため、失敗時に状態が中途で破損しにくい。
2.1.2 整合性
整合性は、トランザクションが適用された結果として、データが定義した制約や業務ルールを満たすことを指す。参照整合性、金額整合、状態遷移の正当性といった観点が含まれる。
2.1.3 分離性
分離性は、複数トランザクションが同時に動作しても、互いの影響が「指定した見え方」に従って抑制される性質である。完全に独立にするほど厳密になりやすいが、その分だけ性能への影響も増えがちである。
2.1.4 耐久性
耐久性は、コミットされた結果が障害後も保持されることを意味する。永続化やログ記録などにより、プロセス障害や電源断のような状況でも復元可能にする。
2.2 コミットとロールバック
コミットは、トランザクションの変更を確定し、以後は取り消さないことを宣言する操作である。ロールバックは、実行途中で問題が起きた際に、変更を無かったことにして状態を元に戻すための手段である。運用上は例外や異常検知に応じて使い分ける。
2.3 正常終了と異常終了の扱い
正常終了とは、処理条件を満たし、制約違反がなく、結果が期待どおりである状態を指す。異常終了は、論理的な検証失敗(入力不正、整合性条件違反)や実行上の問題(タイムアウト、通信切断、リソース不足)などが含まれる。システムは異常時にロールバックやリカバリ手順へ移行し、未確定な変更を残さないことが重要になる。
3 実装方式と制御手法
3.1 ロックによる制御
ロックは、共有資源(行やページ、あるいは論理的な単位)への同時アクセスを調整する手段である。競合を抑えることで分離性を実現しやすい一方、待ちやすさ、スループット低下、デッドロックの可能性といった課題がある。
3.1.1 排他ロックと共有ロック
排他ロックは、対象への書き込みを行うトランザクションに付与し、他の書き込みや場合によっては読み取りも制限する。共有ロックは読み取り用で、複数の読取を同時に許容しつつ、書き込みをブロックするなどの使い分けが行われる。
3.1.2 デッドロックと回避・検出
デッドロックは、複数トランザクションが互いの解放待ち状態になり、進行不能になる現象である。回避策としては、取得順序を統一する、必要最小限の粒度でロックする、長時間処理を避けるなどがある。検出策としては、待ちグラフの監視やタイムアウトによる中断が用いられる。
3.2 楽観的制御
楽観的制御は、競合が頻繁に起きない前提で進め、コミット直前や確定ポイントで整合性を検証する方式である。ロック待ちを減らしやすいが、競合時には再試行コストが表面化する。
3.2.1 バージョニング
バージョニングは、更新対象に版番号や更新時刻などのメタ情報を付け、更新時に「自分が見た版とまだ同じか」を確認する仕組みである。異なれば他者の更新が介在したと判断し、失敗として扱うことで整合性を守る。
3.2.2 再試行戦略
再試行戦略では、競合検出時に一定回数までやり直す。単純な即時リトライだけでなく、指数バックオフやジッタ付与により集中衝突を緩和する設計が採用されることがある。
3.3 タイムアウトと障害時挙動
タイムアウトは、待機が無限に続く事態を避けるための制限である。ロック待ちやネットワーク応答など、各種の遅延に上限を設定し、超過時には例外として扱ってロールバックやフォールバックへ遷移させる。障害時にはリトライの可否、二重実行の防止、ログ整合性といった点を事前に整理しておく必要がある。
4 分離レベルと整合性のトレードオフ
4.1 分離レベルの考え方
分離レベルは、同時実行下で「読み取り結果がどの程度安定しているか」を段階的に定める概念である。厳しさが増すほど矛盾の種類を減らせる反面、必要な制約や待ちが増えて性能に影響しやすくなる。設計では、業務要件に照らして適切な妥協点を選ぶ。
4.1.1 読み取りの整合性
読み取りの整合性とは、あるトランザクションが観測するデータが、指定した基準に対して矛盾しないことを意味する。たとえば「コミット済みのみを見る」などの定義が実装に落とし込まれる。
4.1.2 同時実行時の現象
同時実行では、書き込み途中の値が見える、読み取り結果が途中で変わる、同じ条件の検索結果が変化するなどの現象が起こり得る。分離レベルは、これらの現象をどこまで許容するかを調整する。
4.2 代表的な読み取り異常
4.2.1 ダーティリード
ダーティリードは、まだコミットされていない更新内容を別トランザクションが読み取ってしまう状態を指す。ロールバックが起きると、参照していた値が実在しなかったことになるため注意が必要となる。
4.2.2 ファントムリード
ファントムリードは、条件に一致する行の集合が、同一トランザクション内で読み直した際に変わってしまう現象である。たとえばある条件の範囲内に新規行が挿入され、再検索すると「新しく見える」などが該当する。
4.2.3 非反復読み取り
非反復読み取りは、同一トランザクションが同じ行に対して同条件で参照した結果が、読取りのたびに異なる値になってしまう状態である。更新が介在して見え方が揺れるため、アプリケーション側で想定外の分岐が起きる可能性がある。
4.3 性能設計の観点からの選択
性能面では、ロック保持時間、競合頻度、インデックス利用、読み取りパターンなどが分離レベルと相互に影響する。厳格な分離は期待値を安定させる一方で、待機とスループット低下を招き得る。設計では、必要な整合性を最小限のコストで満たす観点から設定する。
5 トランザクションのライフサイクル
5.1 開始から終了までの流れ
一般的な流れは、トランザクション開始→操作の実行→整合性検証→コミット、または失敗時のロールバック、という順で整理できる。開始時点での分離設定や利用する隔離単位(対象行、対象範囲)も、この段階で決まる。
5.2 自動コミットと明示的制御
自動コミットは、単一文単位や簡易処理に対して自動で確定してしまう設定を指す。複数ステップをまとめたい場合は、明示的に開始・終了を管理する方が安全である。選択の判断軸は、失敗時にどこまで巻き戻す必要があるかに置かれる。
5.3 中断・リトライ時の注意点
中断はタイムアウトや例外などで発生し得る。リトライを行う際は、処理が二重に適用されないことが重要であり、冪等性の確保や一意キー、結果の保存方式などを考慮する必要がある。特に外部サービス呼び出しを含む場合、再実行の副作用を抑える設計が求められる。
6 分散トランザクションと関連技術
6.1 なぜ分散で難しくなるか
分散環境では、参加主体が複数であるため、どれか一方が遅延または失敗した時に「全体として確定すべきか」を調整する必要がある。さらにネットワーク遅延、部分障害、メッセージの重複や喪失といった要因が加わるため、単純なコミット/ロールバックだけでは整合を保ちにくい。
6.2 二相コミットの考え方
二相コミットは、全参加者に対して準備段階で「確定可能か」を確認し、その後に確定指示を送ることで整合を取る考え方である。準備段階では確定可能状態を確保し、後段でコミットを実行する。ただし、障害時に待ちが残りやすく、拘束時間が長くなるという運用上の制約が指摘される。
6.3 三相コミットとその位置づけ
三相コミットは、二相コミットで課題になり得る不確定期間を縮めるために、追加の段階を設ける拡張として位置づけられる。状態遷移を増やすことで、参加者が参照すべき情報を整理し、障害時の復帰をしやすくする狙いがある。設計ではオーバーヘッドと信頼性のバランスを評価する。
6.4 サーガ(業務プロセス分割)アプローチ
サーガは、業務を複数の小さな処理列に分け、各ステップで成功を積み上げる一方、後続の失敗時には補償処理で取り消す考え方である。強い同期確定を避けつつ、結果として整合性を保つことを狙うため、分散での現実的な選択肢として用いられることがある。
7 トランザクション設計の実務ポイント
7.1 クリティカル区間の切り方
クリティカル区間は、整合性を崩せない操作の集合として切り出す必要がある。境界を広げ過ぎるとロック時間が長くなり、狭め過ぎると整合性を満たせない。業務ルールと失敗時の巻き戻し要件を基に、最適な範囲を定める。
7.2 トランザクション時間の最適化
実行時間が長いほど競合の可能性が高まり、待機やタイムアウトが増える。計算を外に逃がす、外部呼び出しを縮める、読み取りと書き込みを必要最小にするなど、処理を短縮する工夫が効果的である。トランザクション内で行うべき処理を明確化することが重要になる。
7.3 影響範囲(ロック範囲・書き込み範囲)
ロック範囲は、競合の発生点を左右する。影響を受ける行やインデックスキーを絞れる設計は競合を抑える。書き込み範囲も同様で、更新対象を必要箇所に限るほど他の処理への干渉が減る。実装段階では実測に基づき調整する。
7.4 監視とトラブルシュート
監視では、待機時間、ロック競合、リトライ回数、異常終了率などの指標が役立つ。トラブルシュートでは、ログから失敗の原因を切り分け、デッドロックやタイムアウトの発生条件を特定する。再発防止として分離設定や索引設計、処理順序の見直しが検討される。
8 よくある事例とまとめ
8.1 ECサイトの決済処理例
ECでは、注文確定、支払い受付、在庫引当、請求情報の記録などを分けて考えることが多い。決済が成功した場合のみ注文状態を進め、途中段階で失敗したときは状態を戻す。また外部決済ゲートウェイを含む場合、再試行時の二重請求を避けるための設計(識別子、冪等化)を行う。
8.2 在庫更新と同時実行の例
在庫は同時アクセスが起こりやすい領域である。複数購入が同時に進むと、同じ在庫数を参照して過剰販売につながる危険があるため、更新時に競合検出を行うか、適切なロック戦略で競合を抑える。分離レベルや更新方式(楽観・悲観)を調整し、再試行の結果を業務上許容できる形に整理する。
8.3 Q&A(設計判断の典型)
Q:トランザクションは大きいほど安全ですか。 A:一般に巻き戻し単位は大きくできますが、競合や待機が増えやすく、結果として失敗率も上がり得る。必要な整合性だけを満たす範囲に区切るのが実務的である。
Q:分散でも二相コミットを使えば常に解決しますか。 A:理論上の整合は狙えますが、障害時の拘束や運用負荷が重くなることがある。要件に応じてサーガのような補償型の設計を選ぶ余地がある。
Q:分離レベルはどのように決めるべきですか。 A:必要な読み取りの正しさの度合いと、性能指標、競合の頻度を踏まえて決める。最終的には監視データを基に調整する。