トップ «前の日記(2010年11月12日) 最新 次の日記(2010年11月30日)» 編集

Masa's blog

検索キーワード:

2010年11月29日 データベースの排他制御に関して [長年日記]

_ トランザクション分離レベルはSERIALIZABLEが望ましい by 名も無きCOBOLER

多くのRDBMSではトランザクション分離レベルとSELECT時の排他指定の組み合わせによって、排他制御の挙動にいくつかのパターンがある。

トランザクション分離レベルに関しては一般的に以下の4種類がある。

READ UNCOMMITTED
読み込むデータがコミット前のものである可能性がある。
READ COMMITTED
読み込むデータはコミット済みのものである事が保証される。
REPEATABLE READ
一度読み込んだデータは同一トランザクション内では何度読み直しても内容が変わらないことが保証される。
SERIALIZABLE
同一トランザクション内では同一条件で何度読み直しても検索結果のデータ集合が変わらないことが保証される。

これらの4種が全てのRDMBSで実装されているわけではなく、例えば商用RDBMSの代表としてOracleMicrosoft SQL Server、オープンソースの代表としてPostgreSQLMySQLで共通して指定できるのは、

  • READ COMMITTED
  • SERIALIZABLE

の2種である。

READ COMMITTED

の場合、Dirty readが排除され、

  • 先行トランザクション中で更新(Insert, Delete, Update)されたデータに対する、後続トランザクションからの全てのアクセス(Select, Insert, Delete, Update)は、先行トランザクションが完了(Commit, Rollback)するまで待たされる。
  • 先行トランザクション中で参照(Select)されたデータに対する、後続トランザクションからの全てのアクセス(Select, Insert, Delete, Update)は直ちに(先行トランザクションの完了を待たずに)実行される。

SERIALIZABLE

の場合、READ COMMITTEDの場合と同様にDirty readが排除され、

  • READ COMMITTEDの場合と同様に、先行トランザクション中で更新(Insert, Delete, Update)されたデータに対する、後続トランザクションからの全てのアクセス(Select, Insert, Delete, Update)は、先行トランザクションが完了(Commit, Rollback)するまで待たされる。
  • 先行トランザクション中で参照(Select)されたデータに対する、後続トランザクションからの更新アクセス(Insert, Delete, Update)は、先行トランザクションが完了(Commit, Rollback)するまで待たされる。
  • 先行トランザクション中で参照(Select)されたデータに対する、後続トランザクションからの参照アクセス(Select)は直ちに(先行トランザクションの完了を待たずに)実行される。

結果として、Phantom read、Non-repeatable readが排除される。

参考
Dirty read
コミット前(未確定)のデータを読み込んでしまう現象
Non-repeatable read
同一トランザクション内において、一度読み込んだデータを再度読み込んだ時にその内容が変化している現象
Phantom read
同一トランザクション内において、一度は読み込めなかったデータが再度読み込んだ時には読めてしまう現象

私見

READ COMMITTEDとSERIALIZABLEを比較した場合、SERIALIZABLEの同時実行性能が低いことを欠点として取り上げることが多いようで、実際JDBCのデフォルトもSERIALIZABLEを避けてREAD COMMITTEDになっている。

これは、たとえば単一のテーブルを参照してその結果を返すというような単純かつ参照性能のみに重きを置いたシステム(検索エンジン的な)では妥当な選択と言える。

しかし、一般的に事務系処理の場合、1回のトランザクション中で複数の関連するテーブルを参照&更新し、その過程においても、またその結果においても一切の矛盾が許されないのが当然なので、たとえ同時実行性能を犠牲にしてもSERIALIZABLE以外に選択の余地は無いと考える。

もちろんこの場合、SQLの文法上は1回で実行できる更新命令であっても、あえて複数回のSQL文に分割して更新するといった工夫とともに、適切なタイミングでコッミット命令を実行し、トランザクションの粒度を下げる努力は必要になるだろう。

ちなみに

READ COMMITTEDの場合でも参照命令(SELECT)に排他指定を行うとSERIALIZABLEを指定したのと同様の動きとなる。

これは一見すると参照命令の書きようしだいでREAD COMMITTED的な動きかSERIALIZABLE的な動きを柔軟にコントロールできて良さそうに見えるのだが、本来トランザクション分離レベルなどというものはトランザクション内で(いやもっと言ってしまうとシステム全体で)あれやこれや選択するようなものでは無く、統一した状態であるべきだと考える。

結果としてREAD COMMITTED + 参照命令の排他指定でSERIALIZABLE的な効果を得ようとするのであれば、最初からトランザクション分離レベルをSERIALIZABLEに設定し、参照命令での排他指定を意識する必要の無い状態を選択するべきだろう。

これは余談だが、参照命令(SELECT)の排他指定の方法はRDBMSごとに方言(SELECT ... WITH (HOLDLOCK)とか、SELECT ... FOR UPDATEとか)が有り、その点においても積極的に利用する気にはなれない...。