このセクションでは、InnoDB
で使用されるロックタイプについて説明します。
InnoDB
では、2 つのロックタイプ (共有 (S
) ロックと排他 (X
) ロック) がある標準の行レベルロックが実装されます。
共有 (
S
) ロックでは、ロックを保持するトランザクションによる行の読み取りが許可されます。排他 (
X
) ロックでは、ロックを保持するトランザクションによる行の更新または削除が許可されます。
トランザクション T1
が行 r
に対する共有 (S
) ロックを保持している場合、別のトランザクション T2
からの行 r
に対するロック要求は次のように処理されます。
T2
によるS
ロックに対するリクエストは、すぐに付与できます。 結果として、T1
とT2
の両方がr
上でS
ロックを保持します。T2
によるX
ロックに対するリクエストは、すぐに付与できません。
トランザクション T1
が行 r
上で排他 (X
) ロックを保持している場合は、r
上のいずれかのタイプのロックに対する一部の個別のトランザクション T2
からのリクエストは、すぐに付与できません。 代わりに、トランザクション T2
は、行 r
上でトランザクション T1
のロックが解放されるまで待機する必要があります。
InnoDB
では、行ロックとテーブルロックの共存を許可する複数粒度ロックがサポートされています。 たとえば、LOCK TABLES ... WRITE
などのステートメントは、指定されたテーブルに対して排他ロック (X
ロック) を取得します。 複数の粒度レベルでロックするには、InnoDB
で intention locks を使用します。 インテントロックは、トランザクションが後でテーブルの行に必要とするロックのタイプ (共有または排他) を示すテーブルレベルのロックです。 インテントロックには、次の 2 種類があります:
intention shared lock (
IS
) は、トランザクションがテーブルの個々の行に shared ロックを設定することを示します。intention exclusive lock (
IX
) は、トランザクションがテーブル内の個々の行に排他ロックを設定することを示します。
たとえば、SELECT ... FOR SHARE
は IS
ロックを設定し、SELECT ... FOR UPDATE
は IX
ロックを設定します。
インテンションロックの手順は次のとおりです。
トランザクションは、テーブル内の行に対する共有ロックを取得する前に、まず
IS
ロックを取得するか、テーブルに対して強いロックを取得する必要があります。トランザクションは、テーブル内の行に対する排他ロックを取得する前に、まずテーブルに対する
IX
ロックを取得する必要があります。
次のマトリックスに、テーブルレベルのロックタイプの互換性の概要を示します。
X |
IX |
S |
IS |
|
---|---|---|---|---|
X |
競合 | 競合 | 競合 | 競合 |
IX |
競合 | 互換 | 競合 | 互換 |
S |
競合 | 競合 | 互換 | 互換 |
IS |
競合 | 互換 | 互換 | 互換 |
ロックに既存のロックとの互換性がある場合は、リクエスト元のトランザクションにロックが付与されますが、既存のロックと競合している場合は、ロックが付与されません。 トランザクションは、競合している既存のロックが解放されるまで待機します。 ロックリクエストが既存のロックと競合し、デッドロックが発生するために付与できない場合は、エラーが発生します。
意図的ロックでは、完全なテーブルリクエスト (LOCK TABLES ... WRITE
など) 以外はブロックされません。 意図的ロックの主な目的は、誰かが行をロックしていること、またはテーブル内の行をロックしていることを示すことです。
インテントロックのトランザクションデータは、SHOW ENGINE INNODB STATUS
および InnoDB monitor の出力に次のように表示されます:
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
レコードロックは、インデックスレコードのロックです。 たとえば、SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
では、t.c1
の値が 10
の場合、他のトランザクションによる行の挿入、更新または削除が防止されます。
レコードロックでは、テーブルにインデックスが定義されていなくても必ず、インデックスレコードがロックされます。 このような場合は、InnoDB
によって非表示のクラスタ化されたインデックスが作成され、このインデックスを使用してレコードロックが行われます。 セクション15.6.2.1「クラスタインデックスとセカンダリインデックス」を参照してください。
レコードロックのトランザクションデータは、SHOW ENGINE INNODB STATUS
および InnoDB monitor 出力に次のように表示されます:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
ギャップロックは、インデックスレコード間のギャップのロック、または最初のインデックスレコードの前または最後のインデックスレコードの後のギャップのロックです。 たとえば、SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
では、範囲内の既存のすべての値間のギャップがロックされているため、カラムにそのような値がすでに存在するかどうかにかかわらず、他のトランザクションが 15
の値をカラム t.c1
に挿入できなくなります。
ギャップは、単一のインデックス値、複数のインデックス値にまたがることも、空にすることもできます。
ギャップロックは、パフォーマンスと並列性とのトレードオフの一環であり、一部のトランザクション分離レベルで使用され、ほかでは使用されません。
一意のインデックスを使用して一意の行を検索することで行をロックするステートメントでは、ギャップロックは必要ありません。 (これには、検索条件に複数カラムの一意のインデックスの一部のカラムのみが含まれるケースは含まれません。この場合は、ギャップロックが発生します。) たとえば、id
カラムに一意のインデックスが設定されている場合、次のステートメントで使用されるのは id
の値が 100 の行に対するインデックスレコードロックだけとなり、ほかのセッションがそのレコードの前にあるギャップに行を挿入するかどうかは問題ではなくなります。
SELECT * FROM child WHERE id = 100;
id
にインデックスが設定されていなかったり、一意でないインデックスが設定されていたりすると、このステートメントで先行するギャップがロックされます。
さまざまなトランザクションによってギャップ上に競合するロックを保持できることも、ここで注目するべき点です。 たとえば、トランザクション A はギャップ上に共有ギャップロック (ギャップ S ロック) を保持できる一方で、トランザクション B は同じギャップ上に排他ギャップロック (ギャップ X ロック) を保持します。 競合するギャップロックが許可される理由は、レコードがインデックスからパージされる場合に、さまざまなトランザクションによってレコード上に保持されたギャップロックをマージする必要があるためです。
InnoDB
のギャップロックは「「純粋に阻害」」です。つまり、その唯一の目的は、他のトランザクションがギャップに挿入されないようにすることです。 ギャップロックは共存できます。 あるトランザクションによって取得されたギャップロックによって、別のトランザクションが同じギャップに対してギャップロックを取得することが妨げられることはありません。 共有ギャップロックと排他ギャップロックに違いはありません。 これらは互いに競合せず、同じ機能を実行します。
ギャップロックは明示的に無効化できます。 これは、トランザクション分離レベルを READ COMMITTED
に変更した場合に発生します。 このような状況では、ギャップロックは検索およびインデックススキャン時に無効化され、外部キー制約チェックおよび重複キーチェック時にのみ使用されます。
READ COMMITTED
分離レベルの使用には、他にも影響があります。 一致しなかった行のレコードロックは、MySQL による WHERE
条件の評価後に解除されます。 UPDATE
ステートメントの場合、InnoDB
は最後にコミットされたバージョンが MySQL に返されるように、「半一貫性」読み取りを実行します。これにより、MySQL はその行が UPDATE
の WHERE
条件に一致するかどうかを判断できます。
次のキーロックは、インデックスレコードのレコードロックと、インデックスレコードの前のギャップのギャップロックの組み合わせです。
InnoDB
は、テーブルインデックスを検索またはスキャンするときに、生成されたインデックスレコード上に共有ロックまたは排他ロックを設定するという方法で、行レベルロックを実行します。 したがって、行レベルロックは、実際にはインデックスレコードロックです。 インデックスレコードに対する次のキーロックは、そのインデックスレコードの前の 「gap」 にも影響します。 つまり、ネクストキーロックは、インデックスレコードロックと、そのインデックスレコードの前のギャップに対するギャップロックとを組み合わせたものです。 あるセッションがインデックス内のレコード R
上に共有ロックまたは排他ロックを持っている場合は、別のセッションがインデックスの順番で R
の直前にあるギャップに新しいインデックスレコードを挿入できません。
あるインデックスに値 10、11、13、20 が含まれているとします。 このインデックスで使用可能な次のキーロックは、次の間隔を対象としています。丸カッコは間隔エンドポイントの除外を示し、角カッコはエンドポイントの包含を示します:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
最後の間隔ではネクストキーロックによって、インデックス内の最大値を上回るギャップ、およびインデックス内の実際のどの値よりも大きい値を持つ「最小上限」の擬似レコードがロックされます。 最小上限は実際のインデックスレコードではないため、事実上、このネクストキーロックによってロックされるのは、最大インデックス値のあとにあるギャップのみです。
デフォルトでは、InnoDB
は REPEATABLE READ
トランザクション分離レベルで動作します。 この場合、InnoDB
はネクストキーロックを使用して検索およびインデックススキャンを行うため、ファントム行の発生を回避できます (セクション15.7.4「ファントム行」を参照)。
次のキーロックのトランザクションデータは、SHOW ENGINE INNODB STATUS
および InnoDB monitor の出力に次のように表示されます:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
挿入意図ロックは、行の挿入前に INSERT
操作によって設定されるギャップロックのタイプです。 このロックは、同じインデックスギャップに挿入する複数のトランザクションは、そのギャップ内の同じ場所に挿入しなければ相互に待機する必要がないように、意図的に挿入することを示しています。 値が 4 と 7 のインデックスレコードが存在すると仮定します。 5 と 6 の値をそれぞれ挿入しようとする個別のトランザクションでは、挿入された行の排他ロックを取得する前に、挿入意図ロックを使用して 4 と 7 のギャップがロックされますが、行が競合していないため相互にブロックされません。
次の例は、挿入されたレコードの排他ロックを取得する前に挿入意図ロックを取得するトランザクションを示しています。 この例には、A と B の 2 つのクライアントが登場します。
クライアント A は、2 つのインデックスレコード (90 および 102) を含むテーブルを作成し、100 を超える ID を持つインデックスレコードに排他ロックを設定するトランザクションを開始します。 排他ロックには、レコード 102 の前にギャップロックが含まれます:
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id |
+-----+
| 102 |
+-----+
クライアント B はトランザクションを開始して、ギャップにレコードを挿入します。 トランザクションは、排他ロックの取得を待機している間、挿入意図ロックを取得します。
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
挿入意図ロックのトランザクションデータは、SHOW ENGINE INNODB STATUS
および InnoDB monitor 出力に次のように表示されます:
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000066; asc f;;
1: len 6; hex 000000002215; asc " ;;
2: len 7; hex 9000000172011c; asc r ;;...
AUTO-INC
ロックは、AUTO_INCREMENT
カラムを含むテーブルに挿入されるトランザクションによって取得される特別なテーブルレベルロックです。 もっとも単純なケースでは、あるトランザクションがテーブルに値を挿入している場合に、ほかのトランザクションはそのテーブルへのそれぞれの挿入を待機する必要があるので、最初のトランザクションによって挿入された行が、連続する主キー値を受け取ります。
innodb_autoinc_lock_mode
構成オプションは、自動増分ロックに使用されるアルゴリズムを制御します。 これにより、自動増分値の予測可能なシーケンスと挿入操作の最大同時実行性の間のトレードオフ方法を選択できます。
詳細は、セクション15.6.1.6「InnoDB での AUTO_INCREMENT 処理」を参照してください。
InnoDB
では、空間カラムを含むカラムの SPATIAL
インデックス付けがサポートされています (セクション11.4.9「空間分析の最適化」 を参照)。
SPATIAL
インデックスを含む操作のロックを処理するために、次キーロックは REPEATABLE READ
または SERIALIZABLE
のトランザクション分離レベルをサポートするのに適切に機能しません。 マルチディメンショナルデータには絶対順序付けの概念がないため、「next」 キーは明確ではありません。
SPATIAL
インデックスを含むテーブルの分離レベルのサポートを有効にするために、InnoDB
では述語ロックを使用します。 SPATIAL
インデックスには最小境界矩形 (MBR) 値が含まれているため、InnoDB
では、クエリーに使用される MBR 値に述語ロックを設定することで、インデックスに対する読取り一貫性が強制されます。 他のトランザクションでは、クエリー条件に一致する行を挿入または変更できません。