キー範囲ロック

キー範囲ロックは、SERIALIZABLE トランザクション分離レベルを使用中に、Transact-SQL ステートメントで読み取っているレコード セットに含まれている行の範囲を暗黙的に保護します。SERIALIZABLE 分離レベルでは、トランザクション中に実行されるクエリは、そのトランザクション内で実行されるたびに同一の行セットを取得する必要があります。キー範囲ロックではこの要件を満たすために、新しい行のキーが SERIALIZABLE トランザクションで読み取られるキー範囲内にある場合に、他のトランザクションが新しい行を挿入できないようにします。

キー範囲ロックを使用すると、ファントム読み取りを回避できます。各行のキー範囲を保護することで、トランザクションからアクセスされるレコード セットへのファントム挿入も回避されます。

キー範囲ロックは、キー範囲の開始値と終了値を指定して、インデックスに対して設定されます。このロックでは、範囲内のキー値を持つ行を挿入、更新、または削除する操作がブロックされます。挿入操作、更新操作、または削除操作では、最初にインデックスに対するロックを取得する必要があるためです。たとえば、シリアル化可能なトランザクションでは、'AAA''CZZ' の間のキー値を持つすべての行を読み取る SELECT ステートメントを実行できます。'AAA' から 'CZZ' の範囲内のキー値にキー範囲ロックをかけると、他のトランザクションからは 'ADG''BBD''CAL' など、その範囲内のキー値を持つ行は挿入されません。

キー範囲ロック モード

キー範囲ロックには、範囲-行形式で指定される範囲と行のコンポーネントが含まれています。

  • 範囲は 2 つの連続したインデックス エントリ間の範囲を保護するロック モードを表します。

  • 行はインデックス エントリを保護するロック モードを表します。

  • モードは使用する組み合わされたロック モードを表します。キー範囲ロック モードは 2 つの部分から成ります。最初の部分はインデックス範囲 (RangeT) をロックするのに使用するロックの種類を表し、その次の部分は特定のキー (K) をロックするのに使用するロックの種類を表します。RangeT-K のように、2 つの部分はハイフン (-) で連結されます。

    範囲

    モード

    説明

    RangeS

    S

    RangeS-S

    共有範囲。共有リソース ロック。シリアル化可能範囲スキャン。

    RangeS

    U

    RangeS-U

    共有範囲。更新リソース ロック。シリアル化可能更新スキャン。

    RangeI

    Null

    RangeI-N

    挿入範囲。NULL リソース ロック。新しいキーをインデックスに挿入する前に範囲をテストするのに使用します。

    RangeX

    X

    RangeX-X

    排他範囲。排他リソース ロック。範囲内のキーを更新するのに使用します。

注意

内部 NULL ロック モードは、他のすべてのロック モードと互換性があります。

各キー範囲ロック モードには、重なり合うキーと範囲に対して取得されるロックが、どのロックと互換性があるかを示す互換性マトリックスがあります。ロックの互換性の完全なマトリックスについては、「ロックの互換性 (データベース エンジン)」を参照してください。

 

既に許可されているモード

 

 

 

 

 

 

要求されたモード

S

U

X

RangeS-S

RangeS-U

RangeI-N

RangeX-X

共有 (S)

不可

不可

更新 (U)

不可

不可

不可

不可

排他 (X)

不可

不可

不可

不可

不可

不可

RangeS-S

不可

不可

不可

RangeS-U

不可

不可

不可

不可

不可

RangeI-N

不可

不可

不可

RangeX-X

不可

不可

不可

不可

不可

不可

不可

変換ロック

変換ロックは、キー範囲ロックが別のロックと重なり合うときに作成されます。

ロック 1

ロック 2

変換ロック

S

RangeI-N

RangeI-S

U

RangeI-N

RangeI-U

X

RangeI-N

RangeI-X

RangeI-N

RangeS-S

RangeX-S

RangeI-N

RangeS-U

RangeX-U

変換ロックは、同時実行プロセスを実行しているときなど、さまざまな環境で短時間発生することがあります。

シリアル化可能な範囲スキャン、単一フェッチ、削除、および挿入

キー範囲ロックは、次の操作のシリアル化を保証します。

  • 範囲スキャン クエリ

  • 存在しない行の単一フェッチ

  • 削除操作

  • 挿入操作

キー範囲ロックを実行する前に次の条件を満たしておく必要があります。

  • トランザクション分離レベルを SERIALIZABLE に設定する。

  • クエリ プロセッサではインデックスを使用して範囲フィルタ述語を実装する必要があります。たとえば、SELECT ステートメントで WHERE 句を使用すると、ColumnX BETWEEN N**'AAA'** AND N**'CZZ'** 述語を使用して範囲条件を設定できます。ColumnX がインデックス キーに含まれている場合、キー範囲ロックだけを取得できます。

次のテーブルとインデックスは、この後のキー範囲ロックの例の基準として使用されます。

インデックス B-Tree を持つデータベース テーブルの図

範囲スキャン クエリ

範囲スキャン クエリを確実にシリアル化するには、同じトランザクション内で同じクエリを実行するたびに同じ結果が返されるようにします。他のトランザクションによる範囲スキャン クエリ内に新しい行を挿入しないでください。これはファントム挿入になります。たとえば、上の図のテーブルとインデックスを使用する次のクエリについて考えます。

SELECT name
    FROM mytable
    WHERE name BETWEEN 'A' AND 'C';

名前が Adam と Dale の間に含まれるデータ行の範囲に対応するインデックス エントリにキー範囲ロックを設定し、上のクエリで指定した範囲内に新しい行を追加、削除することを禁止します。この範囲の最初の名前は Adam ですが、このインデックス エントリに対する RangeS-S モードのキー範囲ロックによって、Abigail など英字 A で始まる新しい名前を Adam の前に追加することが禁止されます。同様に、インデックス エントリ Dale に対する RangeS-S キー範囲ロックによって、Clive など英字 C で始まる新しい名前を Carlos の後に追加することが禁止されます。

注意

保持される RangeS-S ロック数は n + 1 個です。n はクエリに該当する行数です。

存在しないデータの単一フェッチ

トランザクション内のクエリで存在しない行を選択しようとする場合、同じトランザクション内で再度そのクエリを実行しても、同じ結果を返す必要があります。どのトランザクションも、存在しない行を追加することはできません。たとえば、次のクエリについて考えてみます。

SELECT name
    FROM mytable
    WHERE name = 'Bill';

名前 Bill は隣接するインデックス エントリである Ben と Bing の間に挿入されるため、この名前範囲に対応するインデックス エントリにキー範囲ロックが設定されます。RangeS-S モードのキー範囲ロックは、インデックス エントリ Bing に設定されます。これにより、Bill などの値がインデックス エントリの Ben と Bing の間に挿入されるのを防ぎます。

削除操作

トランザクション内で値を削除する場合、削除処理を実行するトランザクションの間、値が存在する範囲をロックする必要はありません。シリアル化可能性を維持するには、削除するキー値をトランザクションの終了時までロックするだけで十分です。たとえば、次の DELETE ステートメントについて考えてみます。

DELETE mytable
    WHERE name = 'Bob';

Bob に対応するインデックス エントリに排他 (X) ロックを設定します。他のトランザクションは、削除する値 Bob の前後に値を挿入したり、削除することができます。ただし、値 Bob の読み取り、挿入、または削除を試みるトランザクションは、削除を実行中のトランザクションがコミットまたはロールバックするまでブロックされます。

範囲削除は、行ロック、ページ ロック、またはテーブル ロックの 3 つの基本的なロック モードを使用して実行できます。そのうちどのモードを使用するかは、クエリ オプティマイザにより決定されるか、または ROWLOCK、PAGLOCK、TABLOCK などのオプティマイザ ヒントによってユーザーが指定できます。PAGLOCK または TABLOCK を使用した場合、すべての行をインデックス ページから削除すると、データベース エンジンによりすぐにインデックス ページの割り当てが解除されます。対照的に、ROWLOCK を使用する場合、削除対象のすべての行には削除のマークが付けられるだけです。これらは、後でバックグラウンド タスクによってインデックス ページから削除されます。

挿入操作

トランザクション内で値を挿入する場合、挿入処理を行うトランザクションの実行中、その値が含まれている範囲をロックする必要はありません。シリアル化可能性を維持するには、挿入するキー値をトランザクションの終了時までロックするだけで十分です。たとえば、次の INSERT ステートメントについて考えてみます。

INSERT mytable VALUES ('Dan');

範囲をテストするために、David という名前に対応するインデックス エントリに RangeI-N モードのキー範囲ロックを設定します。ロックが許可されると、Dan が挿入され、値 Dan に排他 (X) ロックが設定されます。RangeI-N モードのキー範囲ロックは範囲のテストだけに必要で、挿入処理を行うトランザクションの実行中は保持されません。他のトランザクションは、挿入する値 Dan の前後に値を挿入したり、前後の値を削除できます。ただし、値 Dan の読み取り、挿入、または削除を試みるトランザクションは、挿入を実行中のトランザクションがコミットまたはロールバックするまでロックされます。