InnoDB
は 2
つのタイプのロックがあるスタンダード行レベルロックを実装します:
共有 (S
)
ロックはトランザクションが行を読むことを許容します。
排他 (X
)
ロックはトランザクションが行を更新、削除することを許容します。
トランザクション T1
が行 r
に対する共有
(S
)
ロックを保持している場合、別のトランザクション
T2
からの行
r
に対するロック要求は次のように処理されます。
T2
による
S
ロック要求は、すぐに許可できます。結果として、T1
と T2
の両方が
r
上で
S
ロックを保持します。
T2
による
S
ロック要求は、すぐには許可できません。
もしトランザクション
T1
が排他
(X
) ロックを行
r
上で保持していたら、そのときは
r
上のロックに対する独特なトランザクション
T2
からのリクエストは直ちに認められません。代わりに、トランザクション
T2
はトランザクション
T1
が行
r
上でそのロックをリリースするのを待たなければいけません。
さらに、InnoDB
はレコードロックの共存とテーブル全体のロックを許容する
複数粒度ロック
をサポートします。複数粒度レベルでのロックを実用的にするために、インテンションロック
と呼ばれるロックの追加タイプが利用されます。インテンションロックは
InnoDB
内のテーブルロックです。インテンションロックの裏にある考えは、あとでトランザクションが、そのテーブル内でどのタイプ
(共有か排他か)
のロックを行のために要求するのかを指示するということです。InnoDB
で使用されるインテンションロックには次の 2
種類があります (トランザクション
T
がテーブル
t
に対する指定された種類のロックを要求したものとする)。
共有インテンション (IS
):
トランザクション
T
はテーブル
t
内の独立行上に
S
ロックを設定する予定です。
排他インテンション
(IX
):トランザクション
T
はそれらの行上に X
ロックを設定する予定です。
たとえば、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
ロックへのリクエストを持ち、またその
S
ロックをクライアント A
がリリースするのを待っているために、ロックリクエストは与えられません。B
による X
ロックのリクエストのために、A
によって保持されている S
ロックを X
ロックにアップグレードするることもできません。結果として、InnoDB
はクライアント A
に対してエラーを生成し、そのロックをリリースします。その時点で、クライアント
B のロックリクエストが与えられ、B
はテーブルから行を削除します。