SGでは、更新のまとまりと同時実行を分けて理解する
現行SGシラバスVer.4.1のデータベース分野には、トランザクション処理、同時実行制御(排他制御)、障害回復などが挙がっています。暗号化やアクセス権だけではなく、業務の途中で処理が失敗したときや、二人が同じ情報を更新するときに、データのつじつまを守る視点も必要です。
本記事は、架空の研修で残り1席へ二人が申し込む例を使います。実在するサイトの予約や不具合、公式試験問題の再現ではありません。説明のためPostgreSQL 18の公式資料を参照しますが、その製品固有の設定やSQLの全仕様がSGで必須、又はこの例が頻出、という意味ではありません。
最初に「どの表の更新を一緒に確定するか」、次に「同じ残席を確認する処理が重なったらどうするか」を考えます。処理をまとめるだけで、あらゆる分離レベルや手順において予約超過が自動的に防げる、と覚えないことが重要です。
更新する表の行の識別と、表同士の対応はSGの主キー・外部キーと参照整合性で、更新をまとめる機能とは別の観点として確認できます。

コミットとロールバック:残席と予約記録を一緒に扱う
架空の研修Xは定員1人で、開始時点の残席は1、予約記録は0件です。取り消しや他の予約経路はないとします。受け付けた一人の予約記録を追加し、残席を0へ更新する二つの処理を、同じデータベースの一つのトランザクションに含める考え方です。片方だけ確定すると、残席と予約記録が食い違います。
コミットは、まとめた更新を確定することです。途中で失敗し、全体をロールバックすれば、そのトランザクションの未確定の表更新を取り消します。この例で他の処理による変更がなければ、残席1・予約0件へ戻ります。データベース全体を昔の時点へ復元する操作とは異なります。
PostgreSQLの資料では、BEGINとCOMMITで処理をまとめ、全体を取り消すときはROLLBACKを使う例が示されています。接続用ライブラリが開始・確定を扱う場合もあるため、実装では実際の境界を確認します。この記事は本番でコマンドを実行する手順ではなく、処理の単位を読むための説明です。
| 状態 | 残席 | 予約記録 | 意味 |
|---|---|---|---|
| 受付前 | 1 | 0件 | まだ誰も予約していない |
| 二つの表更新をコミット | 0 | 1件 | 一件の予約を確定した |
| 途中の失敗で全体をロールバック | 1 | 0件 | この予約の未確定の表更新を取り消した |
一つにまとめても、古い残席の値で書き込むと超過し得る
次は同時実行の条件を明示します。PostgreSQL 18のRead Committedを使い、AとBがそれぞれトランザクションを開始して、通常の読取りで残席1を確認したとします。ここで読取り対象の行を予約のためにロックせず、後でアプリが計算した値を書き込みます。定員と予約件数を別途照合して拒否する仕組みもないという、意図的に不十分な例です。
Aは、読み取った1から1を引いた値0を残席へ書き、Aの予約記録を追加してコミットします。その後、Bも、先に読み取った1を使って計算した固定値0を残席へ書き、Bの別の予約記録を追加してコミットしたとします。最終的には残席0・予約2件となり、定員1人と合いません。
重要なのは「残席を現在値から1減らす」処理ではなく、古い読取り値から求めた固定値0を後で書く、という条件です。この二つを同じ操作として説明しないようにします。AとBそれぞれの二つの更新が一緒に確定していても、業務全体の予約上限が守られたとは限らないことを示すケースです。
読取り
AもBも残席1を見る
両方とも、残席行をロックせずに通常の読取りを行う。
先に確定
Aが固定値0を書き、予約を追加
Aがコミット。ここでは残席0・予約1件。
後から確定
Bも古い値から求めた0を書く
Bも別の予約記録を追加してコミットする。
結果
残席0・予約2件
残席の表示だけでは、定員1人への予約超過を発見できない。
更新をまとめることと、競合する判断を正しく扱うことを分けます。
当サイト独自の架空例。本文の分離レベル・固定値の書込み・追加制約なしという条件に基づく順序図で、全DBMSに共通する実行結果ではありません。
排他制御の例:確認する前に同じ行を保護し、待った後も読み直す
改善例では、全ての予約処理が同じ規則を使い、研修Xの残席行を更新用にロックしてから残席を確認します。Aが先に取得した間、同じ行を更新用に取得しようとするBは待ちます。Aは残席1を確認し、残席の更新と予約記録の追加を同じトランザクションで確定します。
Aのコミット後、Bは更新後の行を取得し、残席0を確認します。Bは満席として予約記録を追加しないため、結果は残席0・予約1件です。反対に、Aが途中で全体をロールバックした場合は、Bが取得後に残席1を確認し、自分の予約を進められる場合があります。待つ前の値を使い続けないことが要点です。
PostgreSQL 18の行ロックの説明では、SELECT FOR UPDATEは同じ行への競合する更新やロック取得を待たせますが、通常の読取りを全て禁止するものではありません。ここではRead Committedの動作を前提にしています。他の分離レベルではエラーになる場合もあり、ロックという言葉だけで全製品の挙動を同じとしないようにします。
- 1
Aが行を取得
同じ研修の行を保護し残席1を確認。競合するBは待つ。
- 2
Aが一緒に確定
残席0とAの予約記録1件を、同じ単位でコミット。
- 3
Bが取得して確認
Aの終了後、Bは更新後の残席0を読み直す。
- 4
Bは追加しない
満席なので予約を追加しない。残席0・予約1件を保つ。
対象・取得時点・確認・確定までの範囲を、一つの手順として読みます。
本文の条件を使った学習用の一方式です。全ての予約経路が同じ保護手順を守る前提で、完全な実装・性能・再試行設計を示すものではありません。
待ち・デッドロック・ロールバックを混同しない
BがAの終了を待つだけなら、それを直ちにデッドロックとは呼びません。例えばAが対象Pを持ってQを待ち、BがQを持ってPを待つように、互いの待ちが循環すると処理が進めなくなります。単に応答が遅いという観測から、デッドロックが原因だと決めることもできません。
PostgreSQLはデッドロックを検出すると、関係するトランザクションの一つを中止して解消します。どちらが中止されるかを業務の前提にせず、複数の対象を扱う場合の取得順序や、失敗後の扱いを設計します。利用者の入力を待ちながら長くロックを持つと、他の処理の待ちを増やし得る点にも注意します。
ロールバックは、送信済みメールや外部サービスで完了した処理まで自動的に取り消す魔法ではありません。PostgreSQLでも採番用のシーケンス値は取り消されないため、番号の欠番だけで予約記録の欠落とは判断できません。本文の表更新が一緒に確定する範囲と、その外側の処理を切り分けます。
ケースを五行の確認表へ落とし、分からない条件を残す
復習では、定員と残席、予約件数を最初に書き、各処理が何を読んで何を書いたかを時系列に並べます。「同時に実行した」とだけ記録するより、AとBが古い値を使ったのか、待った後に値を確認したのかまで書くと、どこで業務の条件が崩れたかを説明できます。
画面へ完了が表示されなかった場合も、通信とデータベースの確定は別の出来事です。利用者の画面だけで失敗と断定し、同じ受付を繰り返すのは避けます。実務では権限を持つ担当者が受付を識別できる記録と確定状況を確認します。この記事では、実システムのDBや保存済みの回答・注文を操作しません。
| 観点 | 確認する内容 | 早合点を避ける点 |
|---|---|---|
| 業務条件 | 定員・残席・予約件数の対応 | 残席0だけで超過なしと決めない |
| 確定単位 | 同じトランザクションに入る表更新 | 一方だけ確定する設計にしない |
| 読取りと書込み | 取得した時点・現在値か固定値か | 二つの異なる更新方法を混同しない |
| 競合 | 同じ対象・取得順序・待った後の確認 | BEGINで囲めば全て安全としない |
| 失敗時 | 未確定の取消し・確定状況・外部処理 | 画面の無応答や欠番だけで失敗としない |
SGの構成・能力・ケース読解とつなげる
データベースが依存先に含まれる構成の読み方はSGのCI・CMDBと構成管理、待ちや処理量を測る際の条件はSGの容量・能力管理へ。構成を記録すること、性能を測ること、更新のつじつまを守ることは異なる観点です。
学習範囲はSGシラバス案内で確認できます。文章の前提を整理する練習は科目Bの読み方へ。登録不要の無料6問は学習画面の体験で、DB分野全体の理解を診断するものではありません。SGの教材・料金で収録範囲と購入条件をご確認ください。