InnoDB
では次のように、何種類かのレコードレベルロックが使用されます。
レコードロック: これはインデックスレコードのロックです。
ギャップロック: これはインデックスレコード間にあるギャップのロック、先頭のインデックスレコードの前や末尾のインデックスレコードのあとにあるギャップのロック、のいずれかです。
ネクストキーロック: これはインデックスレコードに対するレコードロックと、そのインデックスレコードの前にあるギャップに対するギャップロックとを組み合わせたものです。
レコードロックでは、テーブルにインデックスが定義されていなくても必ず、インデックスレコードがロックされます。そのような場合には
InnoDB
によって隠しクラスタインデックスが作成され、このインデックスを使ってレコードロックが行われます。項9.10.1. 「クラスタインデックスと二次インデックス」
を参照してください。
デフォルトでは、InnoDB
は REPEATABLE
READ
トランザクション遮断レベルで動作し、innodb_locks_unsafe_for_binlog
システム変数は無効になっています。この場合、InnoDB
はネクストキーロックを使って検索やインデックス走査を行うので、ファントム行の発生を回避できます
(項9.8.5. 「ネクストキーロックによるファントム問題の回避」を参照)。
ネクストキーロックは、インデックス行ロックとギャップロックを組み合わせたものです。InnoDB
は、それがテーブルインデックスを検索や走査するときに、遭遇したインデックスレコード上で共有または排他ロックを設定する、という方法で行レベルロックを実行します。従って、行レベルロックは実際はインデックスレコードロックであるということになります。さらに、あるインデックスレコードに対するネクストキーロックは、そのインデックスレコードの前の
「ギャップ」
にも影響を与えます。つまり、ネクストキーロックは、インデックスレコードロックと、そのインデックスレコードの前のギャップに対するギャップロックとを組み合わせたものです。あるセッションがインデックス内のレコード
R
上に共有または排他ロックを持っている場合、別のセッションがインデックスの順番で
R
の直前にあたるギャップに新しいインデックスレコードを挿入することはできません。
あるインデックスに値 10、11、13、20
が含まれているとします。このインデックスでは、次の各区間をカバーするネクストキーロックが使用される可能性があります。ここで、(
や )
は区間の端点を含まないことを、[
や ]
は区間の端点を含むことを、それぞれ表しています。
(negative infinity, 10] (10, 11] (11, 13] (13, 20] (20, positive infinity)
最後の区間ではネクストキーロックにより、インデックス内の最大値の先にあるギャップと、インデックス内の実際のどの値よりも大きい値を持つ 「最小上限」 擬似レコードとがロックされます。最小上限は実際のインデックスレコードではないので、結局、このネクストキーロックによってロックされるのは、最大インデックス値のあとにあるギャップのみとなります。
上の例からギャップのカバー範囲は、単一のインデックス値や複数のインデックス値になることも、さらには空になることもあることがわかります。
一意のインデックスを使って一意の行を検索することによって行のロックを行うステートメントでは、ギャップロックは不要となります。たとえば、id
カラムに一意のインデックスが設定されている場合、次のステートメントで使用されるのは
id
の値が 100
の行に対するインデックスレコードロックだけとなり、ほかのセッションがそのレコードの前にあるギャップに行を挿入するかどうかは問題ではなくなります。
SELECT * FROM child WHERE id = 100;
id
にインデックスが設定されていなかったり、一意でないインデックスが設定されていたりすると、このステートメントで先行するギャップがロックされます。
INSERT
操作では行の挿入前に、挿入インテンションギャップロックと呼ばれる一種のギャップロックが設定されます。このロックは挿入する意思があることを表明するものですが、その挿入の際には、同じインデックスギャップ内への挿入を行おうとしているトランザクションが複数存在していても、そのギャップ内の同じ場所に挿入するのでないかぎり、それらのトランザクションは互いに待つ必要はありません。値
4 と 7
のインデックスレコードが存在しているとします。値
5 と 6
の挿入を試みる異なるトランザクションが存在している場合、各トランザクションは挿入行の排他ロックを取得する前に挿入インテンションロックを使って
4 と 7
の間にあるギャップをロックしますが、行が衝突しないので両者の間でブロックは発生しません。
ギャップロックは明示的に無効化できます。これが発生するのは、トランザクション遮断レベルを
READ
COMMITTED
に変更するか、あるいは
innodb_locks_unsafe_for_binlog
システム変数を有効にした場合です。こうした環境下では、ギャップロックは検索やインデックス走査では無効化され、外部キー制約チェックと重複キーチェックでのみ使用されます。
READ
COMMITTED
遮断レベルを使用するか
innodb_locks_unsafe_for_binlog
を有効にした場合の効果はほかにもあります。マッチしなかった行のレコードロックは、MySQL
による WHERE
条件の評価が完了すると解放されます。UPDATE
ステートメントの場合、InnoDB
は 「半一貫性」
読み取りを行って最後にコミットされたバージョンを
MySQL に返すため、MySQL はその行が
UPDATE
の
WHERE
条件にマッチするかどうかを判定することができます。