STEP 3. トランザクションの分離と Isolation Level
3.6 ファントム読み取り(Phantom Read)
ファントム読み取り
ファントム読み取り(Phantom Read)は、一度読み取ったデータが、ほかのトランザクションに よって削除(DELETE)されたり、同じ値が追加(INSERT)されたりするという矛盾です。Phantom は、「幻」や「幻影」という意味で、読み取ったはずのデータが消えていたり、増えていたりする ことから、ファントム読み取りと呼ばれています。
ファントム読み取りでの、「読み取ったデータが DELETE される」という矛盾は、Repeatable
Read レベルで回避できるのですが、「同じ値が INSERT される」という矛盾を回避することは
できません。読み取り時に存在しないデータ(これから INSERT されるデータ)には、ロックの かけようがないからです。これは次のような状況です。
Let's Try
それでは、これを試してみましょう。
1. まずは、ツールバーの[新しいクエリ]から、2つの接続を作ります。
2. 1つ目の接続から、SELECT ステートメントを実行して、「t1」の中から「b='AAA'」のデー タを検索します(COMMIT TRAN を省略して、トランザクション中とします)。
USE sampleDB BEGIN TRAN
SELECT * FROM t1 WHERE b = 'AAA'
3. 次に、2つ目の接続から、データを 1件追加します。
USE sampleDB BEGIN TRAN
INSERT INTO t1 VALUES(5, 'AAA') COMMIT TRAN
SELECT * FROM t1
4. 次に、1つ目の接続から、もう一度「b='AAA'」のデータを検索します。
SELECT * FROM t1 WHERE b = 'AAA'
このように、トランザクション中に読み取ったデータが、1 回目と 2 回目で異なる結果にな る現象(増えたり、減ったりする現象)がファントム読み取りです。
1回目の結果
1回目には存在しな かったデータ
(ファントム値)
矛盾
ファントム読み取りの回避 ~Serializable レベル~
ファントム読み取りを回避できるのが、Serializable レベルです。これは、内部的には次のように ロックの動作を変更することで実現しています。
1. 共有ロックをトランザクションが完了するまで保持
2. 共有ロックを該当データだけでなく、その周囲のデータもロックする(範囲ロックをかけ る)
1 つ目は Repeatatble Read レベルと同じですが、2 つ目の動作が Serializableレベルだけの特 徴です。Serializableレベルでは、同じデータが INSERT されるのを防ぐために、範囲(Range)
ロックを利用しています。
Let's Try
それでは、これを試してみましょう。
1. 前の手順と同じように、ツールバーの[新しいクエリ]から、2つの接続を作ります。
2. 1つ目の接続から、SELECT ステートメントを実行して、「t1」の中から「b='AAA'」のデー タを検索します(COMMIT TRAN を省略して、トランザクション中とします)。このときに、
分離レベルを「Serializable」へ指定します。
USE sampleDB BEGIN TRAN
SELECT * FROM t1 WITH(Serializable) WHERE b = 'AAA'
3. 次に、2つ目の接続から、データを 1件追加します。
USE sampleDB BEGIN TRAN
INSERT INTO t1 VALUES(6, 'AAA') COMMIT TRAN
Serializable を指定
今度は、ロック待ちになり、INSERT ステートメントを実行することはできません。このよ
うに、Serializable レベルを利用すると、読み取り中のデータに対して、同じデータが追加さ
れたり、削除されたりすることを防げるようになります。
Serializable の問題点 ~デッドロックの多発~
Repeatable Read レベルのところで説明したデッドロックが多発する問題は、Serizalizable レベ ルにも当てはまります。したがって、実際に Serizalizable レベルを利用する場面は、ほとんどあ りません。
Note: TransactionScope と COM+コンポーネントのデフォルトは Serializable
前述の Note で記述したように TransactionScope オブジェクトと COM+ コンポーネントのデフォルトの分離レベ ルは、Serizalizable であることに注意する必要があります。
ファントム読み取りを回避するには、アプリケーション側でファントムデータのチェックをしたり、
重複値の INSERT の場合は UNIQUE 制約を利用したり、運用でカバーしたりするようにします。
また、「更新ロック」や、後述の「楽観的同時実行制御」が利用できる場合は、そちらを利用しま す。
Serializable を指定
ロック待ち状態