基礎から理解するデータベースのしくみ(11)トランザクションとは何だろう
初公開日:2006/01/31
マルチユーザー環境では新たな問題が発生ここまでの説明は基本的に,一人のユーザーがデータベースにアクセスしていることを前提にしていました。しかし実際には,複数のユーザーが同時にデータベースにアクセスすることはよくあります。むしろ,受発注システムや座席予約システムなど,実用データベース・アプリケーションのほとんどは,複数のユーザーが同時に使うことを前提にしていると言ってよいでしょう。こうしたマルチユーザー環境では,ユーザーが一人のときには無かったさまざまな問題が起こります。 例えば,ユーザー1がトランザクションの途中で特定のレコードの内容を変更したあとで,別のユーザー2がそのレコードを読み込んだとしましょう。そのあとでユーザー1がトランザクションをロールバックしたら,ユーザー2は,誤ったデータを読み込んだことになります。 あるトランザクションをしているのと別のユーザーが,そのトランザクションでまだコミットしていないデータを読み込んでしまうことを「ダーティ・リード」と呼びます。ダーティ・リードを回避するためには,コミットしていないデータを別のユーザーが読め込めないようにしなければなりません。 コミットしていないデータを外部から読めないようにしても,まだ問題は起こり得ます。先の例でユーザー2が同じレコードを2度読み込むような場合,1度目と2度目の読み込みの間にユーザー1がトランザクションをコミットすると,1度目に読み込んだ内容と2度目に読み込んだ内容が異なる可能性があります。このように,複数回の読み込みの結果が,ほかのトランザクションのコミットのタイミングによって変わってくることを「反復不可能読み込み(non-repeatable read)」と呼びます。 加えて,2回の読み込みの間にユーザー1のトランザクションがレコードを追加したり削除したら,2回目の読み込みでは1回目には無かったレコードが現れたり,それまであったレコードが無くなったりすることになります。この現象を「ファントム」と呼びます 分離性と同時実行性はトレードオフこうした問題を解決するためにもっとも簡単な方法は,それぞれのユーザーが一つひとつ順番に(シリアルに)実行されるようにすることです。そうすれば,トランザクションが互いに影響を及ぼし合うことはなくなります。しかし,それでは単位時間内に実行できるトランザクションの数が少なくなり,パフォーマンスが低下してしまいます。 このように,トランザクション間の分離性と同時実行性には,トレードオフの関係があります*2。そのため現実には,アプリケーションの性格に合わせてトランザクションの分離性と同時実行性のバランスをとらなくてはいけません。 ANSI/ISO*3は,トランザクションの分離の度合い(各トランザクションがそれぞれどの程度互いに影響を及ぼし合うか)を表す指標として,(表1[拡大表示])の四つの「分離レベル」を定義しています。これらは,それぞれ先にあげた「ダーティ・リード」「反復不可能読み込み」「ファントム」の三つの現象が発生するかどうかで区別されます。下にいくほど分離の度合いが高くなり,表の一番上の「未コミット読み込み」ではすべての現象が起こり得ますが,一番下の「シリアライザブル」ではいずれも発生しません。 多くのRDBMSは複数の分離レベルをサポートしており,トランザクションの開始時などに設定が可能です。例えばSQL Serverは表1の四つの分離レベルをすべてサポートしています。一方,Oracleは,コミット済み読み込みとシリアライザブル以外に,更新を行わないトランザクションに使用できる「読取専用」分離レベルを用意しています。いずれのRDBMSも,デフォルトのレベルはコミット済み読み込みです。 ロックがトランザクション分離の基本トランザクション分離するための基本的な方法は,トランザクションが終了(コミットもしくはロールバック)するまでロックをかけることです。ロックは,あるトランザクションがレコードにアクセスしているときに,別のトランザクションからそのレコードにアクセスできないようにする仕組みです(囲み記事「ロックには2種類ある」を参照)。 SQL Serverの場合で説明しましょう。「コミット済み読み込み」分離レベルでは,トランザクションの途中でデータの更新処理を行うと,その時点で取得したロック(排他ロック)をトランザクションの終わりまで保持します。排他ロックでは,そのレコードに対するほかのトランザクションからの読み書きを,ロックがかかっている間は一切禁止します。これによって,ダーティ・リードを防げます。 「反復可能読み込み(repeatable read)」分離レベルでは,あるトランザクションがデータを読み込むと,その時点でロック(共有ロック)を取得し,トランザクションが終わるまで保持します。共有ロックでは,ほかのトランザクションはデータを読み込むことはできますが,データを変更することはできません。したがって,最初のトランザクションが終わるまで,データがほかから書き換えられないことが保証され,反復不可能読み込みは発生しません。 「シリアライザブル」分離レベルを実現するのは少し面倒です。例えば SELECT * FROM emp
といったSQL文に対してファントムが起こらないようにするためには,10から100の間のempnoフィールドを持つレコードを追加したり,削除させないようにしなければなりません。SQL Serverでは,empnoが10〜100に対してキー範囲ロック*4をかけて,ほかのトランザクションがこの間のempnoを持つレコードを追加したり削除できないようにします。 |
ロックには2種類あるロックは,複数のプロセスがテーブルやレコードなどの各種リソースを利用しようとした際の同時アクセスを制限することで,データの不整合性などが生じないようにするための仕組みです。例えば,あるプロセスがデータ書き出しのためレコードに対してロックを取得すると,ほかのプロセスがそのレコードにデータを書き出そうとしても,ロックが解除されるまで待機することになります。テーブルにアクセスする場合,Oracle,SQL Serverのいずれも,デフォルトでレコード単位のロックを行います。 ロックには,大きく分けて「排他ロック」と「共有ロック」の2種類があります。排他ロックは,レコードのデータを変更するために取得するロックで,一つのレコードに対して一つのプロセスだけが取得できます。OracleやSQL Serverでは,あるプロセスがデータを更新する際には自動的に排他ロックを取得します。これによって,データを更新している途中で,ほかのプロセスがデータを読み込んだり,変更できないようになります。 共有ロックは,リソースに対して同時に複数のプロセスが取得できるロックで,基本的に読み込みの際に利用します。あるプロセスが特定のリソースに対して共有ロックを保持していると,ほかのプロセスはそのリソースに対して共有ロックを取得できますが,排他ロックを取得することはできません。共有ロックでは,それぞれのプロセスはロックされているレコードのデータを変更できませんが,読み込むことは可能です。SQL Serverは,データを読み込む際に共有ロックを自動的に取得します。なお,あるプロセスが排他ロックを保持している間,ほかのプロセスは排他ロックも共有ロックも取得できません。 SQL Serverは「更新ロック」と呼ばれるロックも用意しています。更新ロックは働き自体は共有ロックと同じですが,取得できるのは一つのレコードに対して一つのプロセスに限られます。あるプロセスが更新ロックを取得すると,別のプロセスは共有ロックを取得できますが,排他ロックや更新ロックを取得することはできません。主に,あるレコードのデータを読み込んだ後に,排他ロックに切り替えてデータを書き込む,といった用途で使われます。 |