ある町のホテルの空室数をX、この町にいく飛行機の空席数をYとする。
問題2: この場合に平行して動作できる操作を横に並べて、
LX-T RX-W-S RX-W-Tなどと記述することにしよう。もっとも並列度が大きくなるようなデッドロック しない実行順序を示せ。
実際には、トランザクション一つにつきロックを一つだけとるような方法が 簡単であり効果的だ。しかし、この方法は一度にたくさんのアクセスが来る 場合には適さない。
もし、データのアクセス頻度が大きく、デッドロックは稀にしか起きない のならば、「取りあえずロックしていく。 ロックがとれないようだったら、しばらく待って、あきらめる」という方法が ある。(楽観的平行制御 Optimistic Concurrency Control)
この場合、あきらめたトランザクションは、今まで行った作業の取り消し をおこなう必要がある。これをトランザクションの アボート(abort)という。 アボートは、ディスクアクセスなどに失敗した場合、その他、 アプリケーションの都合によっても起きる。
例
前の例題で、飛行機だけ予約が取れても、ほてるだけ予約が取れても、役に たたない。もし、それぞれの残りが一つの場合、SとTが飛行機とホテルを別々に 予約できてしまう。(そして、お互いにキャンセル待ちに入る...) そうしない ためには、飛行機とホテルをセットにすれば良かった。
セットにしない場合でも、飛行機とホテルの予約をとって、どちらかが 取れなかったらアボートするという方法でも良い。この時にも、予約の 順序を決めると、それぞれが一つづつ残っているのに誰も予約できなかった というようなこと防ぐことができる。
2相ロックを使っていれば、通常はロックによるアボートでは、 カスケーディングアボートは起こらない。しかし、その他の要因による アボートではカスケーディングアボートが無制限に起きる可能性がある。
これを防ぐには、2相ロックの解除を徐々にではなく、一度に行えば 良い。(狭義の2相ロック) しかし、この方法もアクセス頻度が大きい場合には並列度を 下げるので嫌われることが多い。
普通、データベースシステムは、どのトランザクションがコミットしたか を記録している。これはログ(log)と呼ばれる。 何もしなければログは どんどん大きくなっていく。カスケーディングアボートに備えるために、 ずーとログを取っておかなくてはならないからである。
ある時点で、データベースの状態をとっておき、それを正規の状態 と決めれば、その時点より前のログを取っておく必要はない。それは バックアップ(backup)と呼ばれる。バックアップの後は、明示的に トランザクションログを消さなくてはならないデータベースも多い。
Unix には、sync というコミットに近い動作がある。これは、 アプリケーションからは書き込みは終わっているが、OSはディスクに データを書き込んでいない状態を解消する。したがって、Unix上の ファイルシステムをもとにしたデータベースは本当の意味で安全になることは ない。多くのデータベースではファイルシステムを経由せずに直接 ディスクを操作することでこれを回避している。
リアルタイムシミュレーションを考えよう。この時、シミュレーシ ョンするイベントは時刻が付いている。シミュレーションする オブジェクトは、状態の変更を関連す るオブジェクトにメッセージとして送る。これでだいたいの場合はうまくい く。気まずいのは自分が既に状態を変更してしまった後に、過去の 時点のメッセージが届いた場合である。 この時に受け取ったものは自分自身をアボートし、アンチメッセージを 送る。アンチメッセージを送るためにオブジェクトはメッセージの 履歴を覚えてなければならない。
これは一種の楽観的平行制御である。この方法では全体に仮想的な 時間(Virtual Time)が流れていて、局所的には時間が逆転する(Time warp) 可能性がある。このシステムでも、ある時点の実行を固定する操作を 導入すれば、覚えておくメッセージを減らすことができる。
この方法は、データベースのアボート、コミットと類似している。
まず、トランザクションは(これは一つのサイトにある)、「コミットするぞ」 というメッセージをすべてのサイトに送る。そして「コミットしていいぞ」 というメッセージが全部のサイトから来た時点で、ログにトランザクションを 書き込む。もし、一つでも「だめだ」という答が来たら、トランザクションは ロールバックする。どちらの場合も、その結果を全部のサイトに伝える。
これらは実際のデータベース、特にSQL levelからは見えない 動作であるが、一応、常識として覚えておこう。
#!/usr/open/bin/perl5 use Msql (); $host = 'bw04'; $database = 'test'; $Db = Msql->Connect($host,$database) or die; $sth = $Db->Query("select * from アンケート\n") or die $Msql::db_errstr; for (0..$sth->numfields-1) { print "type=", $sth->type->[$_]; print "\tlength=",$sth->length->[$_]; print "\tname=",$sth->name->[$_]; print "\n"; } $sth->DataSeek(0); while (@row = $sth->FetchRow) { print "@row\n"; } print "\n";