InnoDB
のレコードレベルのロックには、レコードロック、ギャップロック、ネクストキーロックなどの複数のタイプがあります。共有ロック、排他ロック、およびインテンションロックについては、セクション14.2.3「InnoDB のロックモード」を参照してください。
レコードロック: これはインデックスレコードのロックです。
ギャップロック: これはインデックスレコード間にあるギャップのロック、または先頭のインデックスレコードの前や末尾のインデックスレコードのあとにあるギャップのロックです。
ネクストキーロック: これはインデックスレコードに対するレコードロックと、そのインデックスレコードの前にあるギャップに対するギャップロックとを組み合わせたものです。
レコードロック
レコードロックでは、テーブルにインデックスが定義されていなくても必ず、インデックスレコードがロックされます。このような場合は、InnoDB
によって非表示のクラスタ化されたインデックスが作成され、このインデックスを使用してレコードロックが行われます。セクション14.2.13.2「クラスタインデックスとセカンダリインデックス」を参照してください。
ネクストキーロック
デフォルトでは、InnoDB
は REPEATABLE READ
トランザクション分離レベルで動作し、innodb_locks_unsafe_for_binlog
システム変数は無効になっています。この場合、InnoDB
はネクストキーロックを使用して検索およびインデックススキャンを行うため、ファントム行の発生を回避できます (セクション14.2.7「ネクストキーロックによるファントム問題の回避」を参照)。
ネクストキーロックは、インデックス行ロックとギャップロックを組み合わせたものです。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 の間にあるギャップをロックしますが、行の競合が発生しないため相互にブロックされません。インテンションロックについての詳細は、セクション14.2.3「InnoDB のロックモード」を参照してください。
さまざまなトランザクションによってギャップ上に競合するロックを保持できることも、ここで注目するべき点です。たとえば、トランザクション A はギャップ上に共有ギャップロック (ギャップ S ロック) を保持できる一方で、トランザクション B は同じギャップ上に排他ギャップロック (ギャップ X ロック) を保持します。競合するギャップロックが許可される理由は、レコードがインデックスからパージされる場合に、さまざまなトランザクションによってレコード上に保持されたギャップロックをマージする必要があるためです。
InnoDB
のギャップロックは、「単に抑制的」です。つまり、ほかのトランザクションによるギャップへの挿入が停止されるだけです。したがって、ギャップ X ロックの効果はギャップ S ロックと同じです。
ギャップロックの無効化
ギャップロックは明示的に無効化できます。これは、トランザクション分離レベルを READ COMMITTED
に変更するか、または innodb_locks_unsafe_for_binlog
システム変数 (現在は非推奨です) を有効にすると発生します。このような状況では、ギャップロックは検索およびインデックススキャン時に無効化され、外部キー制約チェックおよび重複キーチェック時にのみ使用されます。
READ COMMITTED
分離レベルを使用するか、innodb_locks_unsafe_for_binlog
を有効にした場合の効果はほかにもあります。一致しない行のレコードロックは、MySQL による WHERE
条件の評価が完了すると解放されます。UPDATE
ステートメントの場合、InnoDB
は最後にコミットされたバージョンが MySQL に返されるように、「半一貫性」読み取りを実行します。これにより、MySQL はその行が UPDATE
の WHERE
条件に一致するかどうかを判断できます。