[技術講座]
わけあって分散トランザクションについて調査する必要があったので、調査した内容を記事にすることにしました。数回にわたって、CORBA、J2EE、Web サービスのそれぞれの分散トランザクション処理について調査した内容を伝えたいと思います。第一回目では、トランザクションとはそもそもどういうものであるのか、その基本について取り上げたいと思います。数ヶ月前は 「とらんざくしょん??」って状態だったので、本記事の内容が必ずしも正しいわけではありません。もし、誤りなどがありましたらご指摘頂ければと思います。では、早速トランザクションについて考えてみましょう。
私は会社に出社する前にコンビニでよくヨーグルトを買います。コンビニに入った後、ヨーグルトコーナーに行って、ヨーグルトを選び(大体、ブルーベリー系)、レジに持っていってお金を支払います。また、図書を購入するときは、Yahoo! オークションを利用します。利用する場合には、購入したい図書に対して入札し、終了時間が来るのを待ちます。途中、何度か他の利用者に入札されることもありますが、そのときは終了間際に入札すれば、落札できる可能性が高いです。運良く希望図書が落札できたら、銀行振込によって入金し、郵便などで図書を受け取ることになります (Yahoo! オークションを利用すると、最新の図書を 30 〜 40 % 割引で購入できることもあるのでお得です! )。
さて、いきなり私のプチプライベートについて紹介しましたが、このようなヨーグルトを買ったり、図書を購入したりするために行う「一連のまとまりのある処理」がトランザクションです。しかし、これは実世界におけるトランザクション(ビジネストランザクションとする)であり、これから取り上げるコンピュータシステムにおけるトランザクション(システムトランザクションとする)とは概念的に異なることに注意する必要があります。
システムトランザクションは、アプリケーションとアプリケーションがアクセスする共有資源(例えば、データベースなど)の観点から処理単位を考えます。例えば、「Yahoo! オークションにて図書を購入する」というビジネストランザクションにおいて、購入したい図書に対して入札を行なう処理は、システムトランザクションとして考えることができます。入札の処理は、アプリケーションとデータベースに影響を与え、また、入札された結果はアプリケーションとデータベース間で一貫していなければなりません。従って、システムトランザクションは、システムで共有される状態を、ある一貫した状態から別の一貫した状態へと変換することを保証する細分化できない作業単位として表されることになります。
ビジネストランザクションとシステムトランザクションの違いについてイメージできましたでしょうか。ビジネストランザクションは取引のことで、 2 つの実体間でお金、物、サービス、情報といったようなものを交換することを表します。一方、システムトランザクションは、ひとまとまりのデータを一貫性の保たれている状態から別の一貫した状態へと変換するオペレーションの集合を表していることになります。
以下、システムトランザクションをトランザクションとして表現します。
トランザクションを上記のように定義すると、トランザクションには ACID 特性と呼ばれる特徴が必要になってきます。ACID 特性という用語は、基本情報処理試験とかにも出てくるので一度は聞いたことはあるでしょう。ACID 特性は以下の原子性、一貫性、分離性、耐久性の英語の頭文字をとったものです。
原子性 |
トランザクションが完全に実行されるか、全く実行されないかの 2 通りの結果しか持たないこと。これは、トランザクションを構成する一連の処理がこれ以上分割できないことを表す。トランザクションの正常な完了をコミット
( commit ) と呼ぶ。また、トランザクションの取り消しをロールバック (
rollback )と呼ぶ。 |
一貫性 ( Consistency ) |
トランザクションがアクセスするデータはある有効な状態から別の有効な状態に変換されなければならないこと。 |
分離性 ( Isolation ) |
トランザクション実行中はそれ自身が他のトランザクションから何の影響も受けず隔離されていなければならないこと。 |
耐久性 ( Durability ) |
どんな障害が発生してもそのトランザクションの結果は永久に保障されていなければならないこと。 |
銀行間の口座振替の処理を例に挙げてトランザクションに ACID 特性が必要な理由を説明しましょう。この例は、トランザクションの図書なら必ず取り上げられる例なのであまり面白くないですが、トランザクションを考える上でとても良い例なのでここでも使いたいと思います。
銀行間での口座振替のトランザクションは以下の 2 つのオペレーションの集合であると見なすことができます。
もし、トランザクションに ACID 特性がなければ、例えば以下のような事態が発生することになるでしょう。
この事態を防ぐには、ACID 特性によって以下のことを保証しなければなりません。
さて、トランザクションに対してこのような ACID 特性を保証するには、トランザクション機能が利用できる環境が必要となります。トランザクション処理モニターはアプリケーションにトランザクション機能を提供する環境の代表例です。他にもリレーショナルデータベースや CORBA 、J2EE、Web サービスがトランザクション機能を提供しているので、これらを利用することによってもトランザクション処理システムを構築することができます。
ただし、トランザクション機能をもつ環境を利用するだけで、 ACID 特性のすべてが保証されるわけではありません。原子性、分離性、耐久性はトランザクション機能をもつ環境によって保証されますが、一貫性に関しては保証されません。なぜなら、一貫性は上記の例からも推測できるようにアプリケーションに強く依存するからです。従って、一貫性に関してはアプリケーションプログラマによって保証されるべき要素となります。
一般的にトランザクションを扱う処理は、以下のようにモデル化することができます [3]。
Begin Transaction // 実行すべき処理 if error, rollback else commit End Transaction
まず、トランザクションを扱う場合は開始を指示する必要があります ( Begin Transaction )。これによって、その後の処理がすべて同じトランザクションとして処理されることになります。トランザクションを開始した後は、トランザクションに制御させたい処理を実行します。そして、その処理を実行したら、エラーが発生していないかどうか調べます。エラーが発生していたら、それまでに行った変更をロールバックして、トランザクションを取り消します。逆に、エラーが発生していなかった場合には、変更をコミットして永続化します。そして、最後にトランザクションを終了して、終了の境界をマークします (End Transaction )。なお、トランザクションの開始と終了を示す手続きを、境界設定といいます。
トランザクションを扱う場合は、どの処理を一つのトランザクションとして扱うかといった境界設定を決定することが必要になります。境界設定の決定を難しくさせることの一つに、現実世界に影響を与える処理との連携があります。例えば、ATM (現金自動支払機) において「お金を支払う」といった回復不可能な処理に対して、トランザクションの境界を決定する場合、その処理をトランザクション内に含める方法(トランザクション 1)と、トランザクション内に含めない方法(トランザクション 2)の 2 通りがあります。しかし、どちらの境界設定を使用したとしても図 1 のようなタイミングで障害が発生した場合には問題が発生します。このような現実世界に影響を与える処理を持ったトランザクションは、一見原子性を保証することができないように思われますが、メッセージキューを利用することによって原子性を保証することができます。詳しくは [1] を見てください。
図 1 現実世界に影響を与える処理を含んだトランザクションの問題
|
では、実際のトランザクション処理とはどのようなものなのか、最も基本的な JDBC を使ったトランザクション処理を見てみましょう [4] 。
Connection con = null; // JDBC ドライバクラスのロード Class.forName("jdbc_driver_class_name"); // データベースへの接続 conn = DriverManager.getConnection("jdbc_url"); // オートコミットモードの解除 conn.setAutoCommit(false); try { // A 銀行の口座から 1000 円を引き出す // B 銀行の口座へ 1000 円を振り込む // コミットする } catch (Exception e) { try { // ロールバックする conn.rollback(); // 口座振替が失敗したという例外をスローします throw new TrasferException(); } catch (SQLException e\se) { // ロールバックが失敗したという例外をスローします throw new RollbackException(); } }
まず、トランザクションの開始ですが、JDBC では明示的なトランザクションの開始 ( Begin
Transaction ) は必要ありません。トランザクションの開始は、前回のトランザクションの終了後、最初の
SQL 文を実行するステートメントオブジェクトのメソッドにより暗黙的に行われます。次にトランザクションの制御ですが、java.sql.Connection
インタフェースの commit()
メソッドと rollback()
メソッドを利用します。java.sql.Connection
のオブジェクトの生成時は、デフォルトで SQL 文を 1 行発行する度にデータベースのコミットを実行するモード(オートコミットモード)に設定されていますので、これを解除するために最初に
Connection オブジェクトに対して
setAutoCommit(false)
メソッドを呼び出します。そして、その後、トランザクションとしての処理を実行します。今回の例では、A
銀行からお金を引き出す作業と B 銀行へお金を振り込む作業を一つのトランザクションとして実行していることになります。正常にトランザクションの処理が終了すれば、commit()
メソッドを実行します。途中、処理に失敗すれば、try
/ catch の例外処理機能を利用して、rollback()
メソッドを実行します。JDBC では、このような処理によってトランザクションの原子性を保証しています。
JDBC には Connection オブジェクトに対して分離性のレベルを設定する機能があります。分離性に関して以下の 5 つのレベルを設定することができます。しかし、本記事は JDBC のトランザクション機能について詳しく解説することが目的ではないので省略します。詳しくは [5] を見てください。
分散トランザクションは、一つのトランザクションが複数のデータベースを参照するようなトランザクションを意味します。分散トランザクションを扱う場合は、JDBC のトランザクション機能だけでは原子性を保証することが困難です。JDBC のトランザクション機能でも二つのデータベースに対して順にコミットを実行すれば原子性が保証されると思われるかもしれませんが、それはデータベースに障害が発生しないことが前提です。
図 2 JDBC トランザクションでは原子性が失われる
|
図 2 は、分散トランザクションを扱うアプリケーションがデータベース A 、データベース B に対してコミットを実行した場合のシーケンスを表しています。分散トランザクションの原子性を保証するには、データベース A とデータベース B に対する変更が、共に反映される(コミット)か、共に反映されない(ロールバック)かのどちらかでなければなりません。しかし、データベース B のコミットに対して失敗したのでロールバックを実行しようとしたとしても、データベース A に対する変更は既にコミットされているため、データベース A とデータベース B の状態を完全に元に戻すことは不可能です。つまり、複数のデータベース間をまたぐ分散トランザクションに関しては、単純な JDBC のトランザクション機能だけでは原子性を保証することができないということです。
では、複数のデータベース間を参照するような分散トランザクションに対して原子性を保証するにはどうすればよいのでしょうか。ここで 2 フェーズコミットプロトコルが登場してようやく分散トランザクションに入っていけるのですが、2 フェーズコミットプロトコルに関しては次回で取り上げることにします。
© 2003 OGIS-RI Co., Ltd. |
|