Oracleではこのような場合も意識せずCOMMIT済みのデータを返してくれる。
色々調べてみると、基本的にロックの設計思想の違いで発生する事象のようなので、以下にまとめておく。
※SQLServerは2005、Oracleは11g
前提としてトランザクション分離レベルが話に絡んでくるのでちょっとまとめる。
| READ UNCOMMITTED | ・・・ | 他のトランザクションによって変更中のデータを読み取ることが可能 |
| COMMITされていないデータも読み取る (ダーティリード) | ||
| READ COMMITTED | ・・・ | COMMIT 済みのデータだけを読み取る |
| SQL Server および Oracle のデフォルト設定 | ||
| REPEATABLE READ | ・・・ | トランザクション内で参照したデータは、トランザクションが終了するまで、他のトランザクションからは変更不可 |
| SERIALIZABLE | ・・・ | トランザクション内で参照したデータは、トランザクションが終了するまで、他のトランザクションからは変更不可 |
| 参照に使用した条件に該当するデータも新規追加不可 |
■SQLServerのロック
SQLServerでは「READ COMMITTED」がデフォルトの分離レベルとして設定されている。
上記説明通り「COMMIT済みのデータだけを読み取る」のだが、その読み取り方がREAD_COMMITTED_SNAPSHOT データベースオプション」の設定(ON,OFF)によって2種類の動作が可能になるというところがミソである。
この動作がOracleにはないのでちょっと戸惑うポイントだと思う。
※READ_COMMITTED_SNAPSHOT オプションはデータベース オプション。
データベース単位で ON,OFF が可能。
・READ_COMMITTED_SNAPSHOT オプションがOFFの場合
このオプションがOFFになっていた場合にデータ参照すると(Selectを発行すると)
☆共有ロックを取得する。
☆参照が完了すると共有ロックを開放する。
という動作になる。
そのため、参照するデータが他のトランザクションで更新中の場合は、更新処理で取得した排他ロックが参照データにかかっており共有ロック取得待ちとなる。
"他のトランザクション"がCOMMITされるまで「待ち」の状態となるため、アプリケーションでSQLServerに接続している場合などタイムアウトでエラーが返ることがある。
・READ_COMMITTED_SNAPSHOT オプションがONの場合
このオプションがONの場合、動作的にはOracleに近くなる。
データ変更する際、変更前のデータ(最新のCOMMIT済みデータ)をtempdbにコピーし、更新を実行する。この更新中〜COMMITまでの間に発生したSelectは(実テーブル行には)排他ロックがかかっているためtempdbを参照し結果を返す。
OracleのUNDOセグメント的な使い方に近い。
[注意]
データ更新中に別トランザクションでそのデータを2回参照する際に1回目の参照後に更新処理がCOMMITされた場合、1回目の参照結果と2回目の参照結果が異なることになる。
Oracleで言うところの「読み取り一貫性」です。
※更新中テーブルで排他制御がかかってる(正確には対象行ですが)時に
更新前データを読み取れる機能
また、ちょっと話は逸れるがSQLServerではロックエスカレーションという機能がある。
行ロックをたくさん取得するより対象行がある表をまるごとロックする方が効率的と判断した場合はロックをエスカレーション(行⇒表)する動作である。
これが思わぬ動作になることがある。
「効率的と判断」する基準が厳しく、SQLServerはロックエスカレーションが多発して使い物にならないという印象を抱かれることが案外多い。
■Oracleのロック
Oracleは早い話が
「ロックが掛けられた行を他のユーザ(トランザクション)が参照しても、ロック解除まで検索を待つことはない」
という仕組みがちゃんとある。
読み取り一貫性というやつである。
では、そのメカニズムを見ていこう。
OracleではDML文の発行時に、自動的に以下の2種類のロックが取得される。
・行に対する排他行ロック・・・同一レコードに対するDML文を防止
・表に対する共有表ロック・・・同一表に対するDDL文を防止
これだけからも分かる通りSelectを防止するロックは一切かからない。
また、DML文を発行するとデータベース・バッファキャッシュにUNDOセグメントが割り当てられ、変更前データがそのUNDOに格納される。その後バッファキャッシュ内のデータを変更する。
その変更が確定するまで(=COMMITされるまで)は
・変更したユーザは変更後のデータ:バッファキャッシュの内容
・その他のユーザは変更前のデータ:UNDOの内容
を参照することで読み取り一貫性を実現している。
※UNDOセグメント
トランザクションで変更する前のデータを格納する領域
トランザクションを開始するとOracleによって自動的に割り当てられる
SQLServerでREAD_COMMITTED_SNAPSHOT オプションをONにした時と非常に動作が似ていることが分かる。
■結論
どっちがいいというより設計思想の違いからくる動作だが、SQLServerでREAD_COMMITTED_SNAPSHOTオプションをOFFにし、updateやinsert中のSelectでロック解除待ちになると、アクセスの多いWebアプリケーションなんかではエラーが頻発する恐れがある。
READ_COMMITTED_SNAPSHOT オプションをONにすればいいのだろうけど、頻繁にDMLが走るアプリケーションなどtempdbにコピーするオーバヘッド相当大きくなるんじゃないかなぁと考えてる。
そもそもデフォルトでONになっていないところに実はMicrosoftも
「DBの規模が大きいとオーバヘッドを無視できないと思っているのではないか」
と勘ぐりたくなってしまう。


