いわゆる「ファントム」の問題がトランザクション内で発生するのは、同じクエリーから生成される一連の行が、実行のたびに異なるような場合です。たとえば、SELECT
が 2 回実行されたが、1
回目には返されなかった行が 2
回目には返された場合、その行が
「ファントム」 行です。
child
テーブルの
id
カラム上にインデックスが設定された状態で、次のように識別子の値が
100
より大きいすべての行をテーブルから読み取り、その選択された行の特定のカラムをあとで更新できるようにそれらの行をロックするものとします。
SELECT * FROM child WHERE id > 100 FOR UPDATE;
クエリーは、id
が 100
より大きい最初のレコードからインデックスを走査します。このテーブルには
id
の値が 90 と 102
の行が格納されているものとします。その走査範囲内のインデックスレコード上に設定されたロックがギャップ
(この場合のギャップは 90 から 102 まで)
への挿入を禁止しなければ、id
が 101
の新しい行を、別のセッションからそのテーブルに挿入することができます。同じトランザクション内で同じ
SELECT
を実行すると、クエリーから返された結果セット内に、id
が 101 の新しい行 (「ファントム」)
が含まれています。一連の行をデータ項目とみなせば、この新しいファントムの子は、「トランザクションの実行は、読み取るデータがトランザクション中は変化しないような方法で行えるべきである」というトランザクションの遮断原則に違反していることになります。
ファントムの発生を回避できるように、InnoDB
では通常、インデックス行ロックとギャップロックを組み合わせた「ネクストキーロック」と呼ばれるアルゴリズムが使用されます。InnoDB
は、それがテーブルインデックスを検索や走査するときに、遭遇したインデックスレコード上で共有または排他ロックを設定する、という方法で行レベルロックを実行します。従って、行レベルロックは実際はインデックスレコードロックであるということになります。さらに、あるインデックスレコードに対するネクストキーロックは、そのインデックスレコードの前の
「ギャップ」
にも影響を与えます。つまり、ネクストキーロックは、インデックスレコードロックと、そのインデックスレコードの前のギャップに対するギャップロックとを組み合わせたものです。あるセッションがインデックス内のレコード
R
上に共有または排他ロックを持っている場合、別のセッションがインデックスの順番で
R
の直前にあたるギャップに新しいインデックスレコードを挿入することはできません。
When InnoDB
がインデックスを走査するとき、インデックス内の最後のレコードの後のギャップをロックすることもできます。上の例ではまさにそれが行われています。id
が 100
より大きい範囲でテーブルへの一切が挿入できないように、InnoDB
によって設定されるロックには、id
値 102
のあとのギャップに対するロックも含まれています。
アプリケーション内に一意性確認を実装するためにネクストキーロックを利用することができます:共有モードでデータを読み取り、挿入しようとする行に重複が見られなければ、行を確実に挿入できます。また、読み取り中は対象となる行の後続の行にネクストキーロックが設定されて、第三者による重複行の挿入を防ぎます。このように、ネクストキーロックによって、テーブル内に存在しないものを 「ロック」 することができます。
ギャップロックは、項9.8.4. 「InnoDB
レコード、ギャップ、およびネクストキーロック」で説明した方法で無効にすることができます。そうした場合、ファントムの問題が発生する可能性があります。なぜなら、ギャップロックが無効だとほかのセッションが新しい行をギャップに挿入できるからです。