図3●口座間の振り込みの処理でデッドロックが起こる様子
図3●口座間の振り込みの処理でデッドロックが起こる様子
[画像のクリックで拡大表示]
図4●図3のSQL文を実行してデッドロックが起こったときのSQL*Plusの画面
図4●図3のSQL文を実行してデッドロックが起こったときのSQL*Plusの画面
[画像のクリックで拡大表示]
両すくみで処理が停止することも

 ロック機能は複数のユーザーが利用するデータベース・アプリケーションには不可欠なのですが,新たな問題の元にもなります。それは「デッドロック」と呼ばれる現象です。

 先の銀行口座間の振り込みの例で次のような場合を考えてください(図3[拡大表示])。トランザクション1は,口座番号10の口座から口座番号20の口座に1万円を振り込もうとしています。一方,トランザクション2は,これとは逆に口座番号20の口座から口座番号10の口座に5000円を振り込みます。ここで仮に,トランザクション1が,(1)口座番号10のレコードを変更してから,トランザクション2が(2)口座番号20のレコードを変更したとしましょう。この時点で,トランザクション1は口座番号10のレコードを,トランザクション2は口座番号20のレコードを排他ロックします。これらのレコードは,トランザクションが終了するまでロックされ続けることになります。

 その後,トランザクション1は,口座番号20のレコードを変更しようとしますが,そのレコードにはトランザクション2が排他ロックを掛けているため,待機状態になります。一方,トランザクション2は,口座番号10のレコードを変更しようとしますが,このレコードはトランザクション1が排他ロックを掛けているため,やはり待機状態になってしまいます。

 このように二つのトランザクションが両すくみの状態になってしまうと,もはやこれ以上処理が進むことはありません。トランザクション1がレコード20のロックを取得するためには,トランザクション2が終了しなければなりません。しかし,トランザクション2が終了するためには,トランザクション1が終了してレコード10のロックが解除される必要があるからです。

 二つのトランザクションが,互いに相手の所有するロックが解除されるのを待機して処理が進まなくなる状態をデッドロックと言います。デッドロックは,必ずしもトランザクションが関係しなくても,複数の処理が同時に実行されている状態では発生する可能性があります。ただし,トランザクションがあるとロックの期間が長くなりがちなため,発生しやすいと言えるでしょう。

 いったんデッドロックに陥ったトランザクションは永久に待ち状態になってしまい,外部からの介入なしには抜け出すことができません。そこで多くのRDBMSは,デッドロックを自動的に検出して解決する仕組みを備えています。こうしたRDBMSは,ロックを待機しているプロセス*8のリストを調べてループ状態になっていないかどうかをチェックしたり,タイムアウト(時間切れ)を利用してデッドロックを検出します。

 デッドロックに陥っているトランザクションがわかれば,その一方に対して待機中のSQL文を強制的に失敗させて,待機状態から抜け出させます。その後,そのプロセスがトランザクションをロールバックしてロックを解放すれば,もう一方のプロセスはロックを取得して処理を再開できるというわけです。

 実験をしてみましょう。Oracleの付属ツールSQL*Plusを二つ起動し,一方に図3のトランザクション1のSQL文を,もう一方にトランザクション2のSQL文を(1)~(4)の順に入力してみてください*9。二つのウィンドウがしばらく入力を受け付けなくなるはずです。

 でも,ご心配なく。しばらくすると,一方に「ORA-00060: リソース待機の間にデッドロックが検出されました。」というメッセージが表示されます(図4[拡大表示])。ここでROLLBACKと入力して処理を中断すれば,もう一方のウィンドウの処理が再開されて正常に終了します。

デッドロックが起こらないようにする

 デッドロックが発生すると,長い待ち時間が生じるうえにロールバックによるコストもかさみます。したがって,デッドロックはできるだけ発生しないようにしなければなりません。そのためにはまず,一つのトランザクションに要する時間をできるだけ短くするのが基本です。先に書いたように,いったん排他ロックを取得したプロセスは,トランザクションが終了するまでロックを保持し続けます。トランザクションが長ければ長いほどロックしている期間は長くなり,それだけデッドロックが発生する可能性は高くなります。

 特に,トランザクションの途中でユーザー入力を待つようなことはできるだけ避けるべきでしょう。一般にユーザー入力はコンピュータが行う処理と比較してはるかに時間がかかるからです。ユーザーが入力の途中で昼食に出掛けてしまうかもしれません。その場合は,何10分もロックが保持されることになります*10

 複数のテーブルをアクセスする処理がアプリケーションのいたるところに現れるような場合は,アクセスする(ロックを取得する)テーブルの順序を同じにしておく,というのも効果があります。例えば,マスター・テーブルと明細テーブルに連続してアクセスすることが多ければ,「アクセスはマスター・テーブル→明細テーブルの順に行う」と決めておくわけです。図3の例からわかるように,デッドロックの多くは二つのプロセスが複数のリソース(図3では口座番号10と20のレコード)を逆の順序でロックしようとすることから生じます。順序を決めておけば,こうしたデッドロックは発生しなくなります。

☆            ☆            ☆

 RDBMSはここで説明したほかにも,さまざまな機能を備えています。例えば,分散している複数のデータベースを更新する場合には,互いの内容の整合性を確保するため「2フェーズ・コミット」という機能を利用します*11。RDBMSのすべての仕組みを一度に理解する必要はありませんが,折に触れて「この機能はどういうしくみで実現されているのか」ということを調べてみてはいかがでしょうか。