InnoDB
では、2 つのロックタイプ (共有 (S
) ロックと排他 (X
) ロック) がある標準の行レベルロックが実装されます。レコード、ギャップ、およびネクストキーの各ロックタイプについては、セクション14.2.6「InnoDB のレコード、ギャップ、およびネクストキーロック」を参照してください。
共有 (
S
) ロックでは、ロックを保持するトランザクションによる行の読み取りが許可されます。排他 (
X
) ロックでは、ロックを保持するトランザクションによる行の更新または削除が許可されます。
トランザクション T1
が行 r
に対する共有 (S
) ロックを保持している場合、別のトランザクション T2
からの行 r
に対するロック要求は次のように処理されます。
T2
によるS
ロックに対するリクエストは、すぐに付与できます。結果として、T1
とT2
の両方がr
上でS
ロックを保持します。T2
によるX
ロックに対するリクエストは、すぐに付与できません。
トランザクション T1
が行 r
上で排他 (X
) ロックを保持している場合は、r
上のいずれかのタイプのロックに対する一部の個別のトランザクション T2
からのリクエストは、すぐに付与できません。代わりに、トランザクション T2
は、行 r
上でトランザクション T1
のロックが解放されるまで待機する必要があります。
インテンションロック
さらに、InnoDB
では、レコードロックとテーブル全体のロックが共存することを許可する複数粒度ロックもサポートされています。複数粒度レベルでのロックを実用的にするために、インテンションロックと呼ばれる追加のロックタイプが使用されます。インテンションロックとは、あとでトランザクションがそのテーブル内の行で必要となるロックのタイプ (共有または排他) を示す InnoDB
のテーブルロックです。トランザクション T
がテーブル t
上に指定されたタイプのロックをリクエストしたと仮定すると、InnoDB
で使用されるインテンションロックには、次の 2 つタイプがあります。
たとえば、SELECT ... LOCK IN SHARE MODE
は IS
ロックを設定し、SELECT ... FOR UPDATE
は IX
ロックを設定します。
インテンションロックの手順は次のとおりです。
トランザクションがテーブル
t
のある行のS
ロックを取得するには、まずt
のIS
またはそれより強いロックを取得する必要があります。トランザクションがある行の
X
ロックを取得するには、まずt
のIX
ロックを取得する必要があります。
これらのルールをまとめる際は、次に示すロックタイプ互換性マトリクスを使用すると便利です。
X |
IX |
S |
IS |
|
---|---|---|---|---|
X |
競合 | 競合 | 競合 | 競合 |
IX |
競合 | 互換 | 競合 | 互換 |
S |
競合 | 競合 | 互換 | 互換 |
IS |
競合 | 互換 | 互換 | 互換 |
ロックに既存のロックとの互換性がある場合は、リクエスト元のトランザクションにロックが付与されますが、既存のロックと競合している場合は、ロックが付与されません。トランザクションは、競合している既存のロックが解放されるまで待機します。ロックリクエストが既存のロックと競合し、デッドロックが発生するために付与できない場合は、エラーが発生します。
したがって、インテンションロックでは、完全なテーブルリクエスト (LOCK TABLES ... WRITE
など) 以外は何もブロックされません。IX
および IS
ロックの主な目的は、だれかが行をロックしていることや、テーブル内の行をロックしようとしていることを示すことです。
デッドロックの例
次の例は、ロックリクエストによってデッドロックが発生したときに、どのようにエラーが発生するのかを示しています。この例には、A と B の 2 つのクライアントが登場します。
最初に、クライアント A が行を 1 つ含むテーブルを作成し、トランザクションを開始します。トランザクション内で、A は共有モードで選択した行で S
ロックを取得します。
mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)
mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;
+------+
| i |
+------+
| 1 |
+------+
1 row in set (0.10 sec)
次に、クライアント B がトランザクションを開始し、テーブルから行を削除しようとします。
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> DELETE FROM t WHERE i = 1;
削除操作を行うには、X
ロックが必要です。クライアント A が保持している S
ロックとの互換性がないために、ロックを付与できません。そのため、リクエストはその行のロックリクエストのキューに入れられ、クライアント B はブロックされます。
最後に、クライアント A もテーブルから行を削除しようとします。
mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
クライアント A は行を削除するために X
ロックが必要であるため、ここでデッドロックが発生します。ただし、クライアント B はすでに X
ロックに対するリクエストを持っていて、クライアント A がその S
ロックを解放するまで待機しているため、そのロックリクエストを付与することはできません。B による X
ロックに対する以前のリクエストが原因で、A が保持している S
ロックを X
ロックにアップグレードすることもできません。その結果、InnoDB
はクライアントのいずれかに対してエラーを生成し、そのロックを解放します。クライアントは、次のエラーを返します。
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
この時点で、ほかのクライアントに対するロックリクエストを付与できるようになり、テーブルから行が削除されます。
InnoDB Monitor の出力の LATEST DETECTED DEADLOCK
セクションには、「TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH, WE WILL ROLL BACK FOLLOWING TRANSACTION」 というメッセージが含まれます。これは、待機リスト上のトランザクション数が 200 の制限に達したことを示します。この制限は、LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK
で定義されます。200 個のトランザクションを超える待機リストはデッドロックとして処理され、待機リストをチェックしようとするトランザクションはロールバックされます。
ロックスレッドが待機リスト上のトランザクションが所有する 1,000,000 個を超えるロックを参照する必要がある場合も、同じエラーが発生する可能性があります。1,000,000 個のロック制限は、LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK
で定義されます。