本記事では、複数の利用者が同時にデータベースへアクセスしてもデータの整合性が崩れないようにする「同時実行制御(排他制御)」の考え方を取り上げ、その前提となるトランザクションとACID特性、注意すべきデッドロック、さらに分散環境で使われる2相コミットメントについて、イメージしやすい形で解説します。
1. 複数ユーザーからのアクセスをどう守るか

サーバの周りには「予約」「入金」「在庫更新」などの吹き出しが飛び交っています。この章では、このような「みんなが同時に使う」状況で、なぜ同時実行制御(排他制御)が必要なのかを整理します。
同時実行制御と排他制御の概要
現代の業務システムでは、複数の利用者が同じデータベースに同時アクセスするのが当たり前になっています。例えば、ネット通販サイトであれば、全国の利用者が同じ瞬間に注文ボタンを押すかもしれませんし、店舗のPOSレジからも売上データが次々に送られてきます。もし、こうしたアクセスを何も考えずにそのまま通してしまうと、「同じ商品が二重に売れてしまう」「残高が正しく更新されない」といった不整合が簡単に発生してしまいます。
そこで登場するのが、同時実行制御(排他制御)です。同時実行制御とは、複数の処理が同じデータにアクセスするときでも、結果の整合性が保たれるように順番や組み合わせを調整する仕組みの総称です。代表的な方法として、ある処理がデータを更新している間は、他の処理がそのデータを書き換えられないよう「ロック(鍵)」をかける排他制御があります。これにより、「一つのデータに対する更新は、必ず一件ずつ順番に行われる」ことが保証され、整合性の崩れを防ぐことができます。
2. トランザクションとACID特性を理解する

この章では、同時実行制御の土台となるトランザクションという考え方と、それを正しく扱うためのACID特性について説明します。
トランザクション
トランザクションとは、データベース上で行われる一連の処理を「ひとかたまりの単位」として扱うための概念です。例えば、銀行振込では、「振込元口座から残高を減らす」「振込先口座の残高を増やす」という二つの更新操作がセットになって初めて意味を持ちます。この二つのどちらか一方だけが行われ、もう一方が失敗してしまうと、どちらかの口座の残高だけが変わってしまい、大問題になります。
このような事態を防ぐために、「一連の処理がすべて成功したときだけ結果を確定し、途中でエラーが起きた場合は最初からなかったことにする」という単位としてトランザクションを定義します。具体的には、トランザクションの開始から終了までの間に行った変更を、一時的に保留しておき、最後に「コミット」という操作で確定させます。途中で問題があれば「ロールバック」を行い、開始前の状態に戻すことで、データベースの整合性を保ちます。
ACID特性
トランザクションが正しく動作しているかどうかを判断するための性質として、有名なものがACID特性です。ACIDは4つの英単語の頭文字をとったもので、それぞれ「原子性」「一貫性」「独立性(分離性)」「永続性」を表します。難しそうな言葉に見えますが、内容はトランザクションに求められる基本ルールを整理したものだと考えると理解しやすくなります。
原子性は、「トランザクション内の処理は全て行われるか、全く行われないかのどちらかであり、中途半端な状態は残さない」という性質です。一貫性は、「トランザクションの前後で、データベースが守るべきルールが破られていないこと」を意味します。独立性(分離性)は、「複数のトランザクションが同時に実行されても、お互いの途中経過に影響されない」こと、永続性は「コミットされた結果は、その後障害が発生しても失われないように保存される」ことを指します。これら4つをしっかり満たすように同時実行制御を行うことで、安心してトランザクションを扱えるようになります。
3. 複数処理が絡み合うときの注意点

この章では、複数のトランザクションを同時に扱う際に注意すべき問題としてデッドロックを取り上げ、さらに複数のデータベースにまたがる処理を確実に行うための2相コミットメントについて解説します。
デッドロック
デッドロックとは、複数のトランザクションが互いに相手の持つロックを待ち合うことで、どちらも先に進めなくなってしまう状態のことです。例えば、トランザクションAが「顧客テーブル」をロックし、次に「注文テーブル」のロックを取りに行こうとしているとします。一方でトランザクションBは先に「注文テーブル」をロックし、その後「顧客テーブル」のロックを取りに行こうとしている状況を考えてみましょう。
この場合、AはBが解放するはずの「注文テーブル」のロックを待ち、BはAが解放するはずの「顧客テーブル」のロックを待つことになり、両者とも永遠に待ち続ける状態に陥ります。これがデッドロックです。デッドロックが発生すると、該当するトランザクションだけでなく、そのロックに影響される他の処理まで止まってしまうため、システム全体の性能や信頼性に悪影響を及ぼします。そのため、多くのデータベースではデッドロックを検出し、一方のトランザクションを強制的にロールバックして解消する仕組みが備わっています。
2相コミットメント
2相コミットメント(2フェーズコミット)とは、複数のデータベースやサーバにまたがるトランザクションを、失敗なく一貫して実行するためのプロトコル(手順)のことです。例えば、本社のデータベースと支社のデータベースにまたがって在庫を更新するような処理では、「本社側だけ成功し、支社側は失敗した」といった不整合が絶対に起きては困ります。このような場面で、2相コミットメントが利用されます。
2相コミットメントでは、簡単に言うと「準備フェーズ」と「コミットフェーズ」の二段階で処理を進めます。まずコーディネータ役のサーバが、関係する全てのデータベースに対して「このトランザクションを実行できそうか」を問い合わせ、各データベースは処理の準備をして「問題なければコミット可能」「エラーがあるのでコミット不可」といった応答を返します。すべてのデータベースから「コミット可能」という返事が揃った場合にのみ、コーディネータが「コミット実行」の指示を出し、各データベースが結果を確定させます。途中で一つでもエラーが報告された場合は、全員でロールバックする方向に切り替えることで、全体としての整合性を守ります。
まとめ
同時実行制御(排他制御)は、複数の利用者が同じデータベースを同時に利用しても、データの整合性が崩れないようにするための重要な仕組みです。ロックなどを用いて更新の順番を調整し、「一つのデータに対する更新は一件ずつ確実に処理する」ことで、二重更新や矛盾した状態の発生を防いでいます。その土台となるのが、処理のひとかたまりを表すトランザクションという単位です。
トランザクションには、原子性・一貫性・独立性・永続性からなるACID特性が求められます。これらを満たすことで、途中で障害が起きても中途半端な状態を残さず、複数のトランザクションが同時に動いてもお互いの結果に悪影響を与えないようにできます。しかし、ロックを使った制御にはデッドロックという落とし穴もあり、システムはこれを検出・解消する仕組みを持っておく必要があります。
さらに、単一のデータベース内だけでなく、複数のデータベースやサーバにまたがる処理を安全に行うためには、2相コミットメントのようなプロトコルも重要になります。すべての場所で処理が成功したときだけ結果を確定し、どこか一つでも失敗したら全体をなかったことにするという考え方は、トランザクションの原子性を分散環境で実現する取り組みだといえます。これらの概念を押さえておくことで、ITパスポート試験の問題だけでなく、実際のシステムがどのようにデータを守っているのかもイメージしやすくなるでしょう。
.png)

コメント