図1●Oracleでトランザクションのロールバックを実現するための仕組み
図1●Oracleでトランザクションのロールバックを実現するための仕組み
[画像のクリックで拡大表示]
リスト1 ●口座間の振り込みの処理を記述した
リスト1 ●口座間の振り込みの処理を記述した
[画像のクリックで拡大表示]

 Part1とPart2では,リレーショナル・データベース管理システム(RDBMS)の基本的な機能であるSQLの解析/最適化やデータ・アクセスの方法について学んできました。RDBMSは,このほかにもいくつか重要な機能を備えています。Part3ではその中からデータの一貫性・整合性を保つための仕組みであるトランザクション処理機能について取り上げます。これは複数のユーザーが同時に利用するようなデータベース・アプリケーションに,なくてはならない機能です。

 トランザクションの概念自体はそれほど難しくはありません。ただ,複数のユーザーが同時にアクセスするような状況では,考慮すべきことがいくつかあります。いくつかの概念を押さえながら,RDBMSがトランザクションをどう処理しているかを見ていきましょう。

複数の処理をひとまとめにして扱う

 まず,トランザクションとは何なのかを簡単に解説しておきましょう。銀行の口座間で振り込み作業をする場合を考えてください。システムが行う処理は,大体以下のような流れになるはずです。

(1)振り込み人の預金から振り込む金額を差し引く
(2)振込先の預金をそのぶん増やす
(3)取引を記録する

このとき,仮に(2)の処理が,口座番号が見つからないとか,途中でシステムがダウンした,などの理由で,正常に終了できなかったとしたらどうなるでしょうか。振り込み人のほうは預金が減っているのにそのお金はどこにも振り込まれていない,といった不整合性が生じてしまいます。

 こうした不整合性が生じないようにするためには,(2)や(3)の処理に失敗したら,(図1[拡大表示])の処理を白紙に戻すようにする必要があります。すなわち,これら(1)~(3)の処理は,本来ひとまとまりの処理として扱うべきもので,処理がすべて実行されるか,それともまったく行われないか,のどちらか一方にしなくてはなりません*1。言い換えれば,処理の一部だけが実行される,というのは許されないのです。

 多くのRDBMSは,こうした目的のために,複数のSQL文からなる一連の処理を,論理的にひとまとめにして扱う機能を備えています。この論理的なひとまとまりの処理をトランザクションと呼びます。トランザクションの中で実行したSQL文の結果は(少なくとも論理的には),すぐにはデータベースに反映されません。「コミット」と呼ぶ操作を行った時点で初めて確定します。

 コミットの反対の操作が「ロールバック」です。ロールバックを行うと,それ以前のトランザクション処理の結果はすべてキャンセルされ,データベースには反映されません。すなわち,データベースはトランザクション処理を開始する前の状態のままです。

 実際に,口座間の振り込みの処理をSQL文で記述してみたのが(リスト1[拡大表示])です(Oracleの場合)。OracleやSQL Serverでは,特に何も指定しなくても,SQL文のならびを自動的にトランザクションとして扱うようになっています。

 トランザクションは,最初に現れた実行可能文から始まります。そして,

COMMIT WORK
と指示された時点で,そのトランザクション内で変更した結果を確定し,データベースに反映します。トランザクションで実行した処理を取り消すなら
ROLLBACK
とすればOKです。コミットもしくはロールバックをすると,その次の実行可能文から自動的に次のトランザクションが始まります。

ロールバックには変更履歴を利用する

 RDBMSが内部でトランザクションを処理する際の動作を見てみましょう。トランザクション処理を実現するための基本的な方法は,トランザクションの中で行われたデータの変更の履歴を保持することです。この履歴を利用すれば,必要に応じてロールバックができます。ここでは,Oracleの場合を例にとって解説しましょう(図1)。

 トランザクションを開始するとOracleはまず,ロールバック用の情報を保存するためディスク領域の一部を,そのトランザクションに対して割り当てます。この領域をロールバック・セグメントと呼びます。そして,トランザクションの中でレコードの追加や更新などの変更を行うたびに

(1)そのレコードを含むデータ・ページの(変更前の)内容をロールバック・セグメントに保存する
(2)REDOログ・バッファに変更の履歴を記録する
(3)データ・ページの内容を更新する(変更後の内容をデータ・ページに書き出す)

という処理を実行します。ここで,REDOログ・バッファは,変更の履歴を記録しておくためのメモリー上のバッファで,後でディスク上のREDOログ・ファイルに内容が保存されます。REDOログ・ファイルは,システムの障害時などにデータベースの内容を復旧するために不可欠です。

 トランザクションをコミットすると,Oracleはトランザクションを識別するための「システム変更番号(SCN)」を割り当て,ロールバック・セグメントにこの番号とコミット済みであることを記録します。そしてREDOログ・バッファの内容を,REDOログ・ファイルに書き出します。これでトランザクションは完了です。

 ロールバックをする場合は,トランザクション中に実行したすべての変更について,ロールバック・セグメントに保存した元のデータを書き戻します。これで,そのトランザクションはなかったことになるわけです。RDBMSによっては,途中のポイントまでロールバックすることも可能です。その場合は,そのポイント以降に行った変更に対し,ロールバック・セグメントの内容をデータ・ページに書き戻します。

 ロールバック・セグメントやデータ・ページへの書き出しは,実際にはいったんメモリー上のキャッシュ・バッファに対して行われ,後でまとめてディスクに反映します。ただし,REDOログ・バッファの内容は,コミット時に必ずディスクに保存します。コミットの直後にシステムがダウンしても,データを復元できるようにするためです。