ロックする読み取り、UPDATE
、または
DELETE
は通常、SQL
ステートメントの処理の中で走査されるすべてのインデックスレコード上にレコードロックを設定します。行を除外する
WHERE
条件がステートメント内に存在するかどうかは、関係ありません。InnoDB
は正確な WHERE
条件を記憶しませんが、どのインデックス範囲が走査されたのかは分かっています。ロックは通常ネクストキーロックになりますが、このロックでは、レコードの直前にある
「ギャップ」
への挿入もブロックされます。ただし、ギャップロックを明示的に無効にし、ネクストキーロックが使用されないようにすることも可能です。詳細は
項9.8.4. 「InnoDB
レコード、ギャップ、およびネクストキーロック」
をご覧ください。
共有ロックと排他ロックの違いについては、項9.8.1. 「InnoDB
ロックモード」
を参照してください。
ステートメントに適したインデックスがなく、MySQL がステートメントを処理するためにテーブル全体を走査しなければいけないなら、テーブルのすべての行がロックされ、その結果、そのテーブルへの別のユーザーによるすべての挿入がブロックされます。クエリーが不必要にたくさんの行を走査することのないように、よいインデックスを作成することが重要です。
SELECT
... FOR UPDATE
または
SELECT
... LOCK IN SHARE MODE
では、走査された行に対してロックが取得されますが、WHERE
節に指定された条件を満たさないなどの理由で結果セットに含める対象から外れた行については、ロックが解放されることが予想されます。ところが場合によっては、クエリーの実行中に結果行とその元のソースとの関係が失われたために行のロックがすぐには解除されない可能性もあります。たとえば
UNION
では、走査 (およびロック)
されたテーブル内の行が、結果セットに含める対象となるかどうかの評価の実施前に、一時テーブルに挿入される可能性があります。この状況では、一時テーブル内の行と元のテーブル内の行との関係は失われているため、クエリー実行が終了するまで後者の行のロックは解除されません。
InnoDB
は次のように特定のロック型を設定します.
検索時に二次インデックスが使用され、かつ設定すべきインデックスレコードロックが排他である場合には、InnoDB
はさらに対応するクラスタインデックスレコードも取得し、それらのレコードにロックを設定します。
SELECT
... FROM
は一貫性読み取りであり、データベースのスナップショットを読み取り、トランザクションの遮断レベルが
SERIALIZABLE
に設定されなければロックは設定しません。SERIALIZABLE
レベルの場合、検索時に直面したインデックスレコード上に共有ネクストキーロックが設定されます。
SELECT
... FROM ... LOCK IN SHARE MODE
では、その検索時に直面したすべてのインデックスレコード上に共有ネクストキーロックが設定されます。
SELECT
... FROM ... FOR UPDATE
は検索で特定されたインデックスレコードに対し、ほかのセッションが
SELECT
... FROM ... LOCK IN SHARE MODE
を実行したり、特定のトランザクション遮断レベルで読み取りを行ったりできないようにします。一貫性読み取りでは、読み取られたビュー内に存在するレコードに設定されたロックはすべて無視されます。
UPDATE
... WHERE ...
は、検索が直面するすべてのレコード上に排他ネクストキーロックを設定します。
DELETE
FROM ... WHERE ...
は、検索が直面するすべてのレコード上に排他ネクストキーロックを設定します。
INSERT
は挿入される行に排他ロックを設定します。このロックはネクストキーロックではなくインデックスレコードロックである
(つまりギャップロックが存在しない)
ため、ほかのセッションは挿入行の前にあるギャップへの挿入を自由に行えます。
行の挿入前に、挿入インテンションギャップロックと呼ばれる一種のギャップロックが設定されます。このロックは挿入する意思があることを表明するものですが、その挿入の際には、同じインデックスギャップ内への挿入を行おうとしているトランザクションが複数存在していても、そのギャップ内の同じ場所に挿入するのでないかぎり、それらのトランザクションは互いに待つ必要はありません。値 4 と 7 のインデックスレコードが存在しているとします。値 5 と 6 の挿入を試みる異なるトランザクションが存在している場合、各トランザクションは挿入行の排他ロックを取得する前に挿入インテンションロックを使って 4 と 7 の間にあるギャップをロックしますが、行が衝突しないので両者の間でブロックは発生しません。
もし重複キーエラーが発生すると、複製インデックスレコード上の共有ロックが設定されます。この共有ロックの使用がデッドロックにつながる可能性があるのは、複数のセッションが同じ行を挿入しようとしているときに別のセッションがすでに排他ロックを取得していた場合です。これは、別のセッションがその行を削除した場合に発生する可能性があります。InnoDB
テーブル t1
の構造が次のようになっているとします。
CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
次に、3 つのセッションが次の処理を順番に実行するものとします。
セッション 1:
START TRANSACTION; INSERT INTO t1 VALUES(1);
セッション 2:
START TRANSACTION; INSERT INTO t1 VALUES(1);
セッション 3:
START TRANSACTION; INSERT INTO t1 VALUES(1);
セッション 1:
ROLLBACK;
セッション 1 による最初の処理では、行の排他ロックが取得されます。セッション 2 と 3 の処理ではどちらも重複キーエラーが発生し、どちらのセッションも行の共有ロックを要求します。セッション 1 はロールバック時に行の排他ロックを解放し、キュー内のセッション 2 と 3 の共有ロック要求が許可されます。この時点でセッション 2 と 3 でデッドロックが発生します。どちらも他方が保持している共有ロックのために、行の排他ロックを取得できません。
キーの値が 1 の行がテーブルに含まれている場合も似たような状況が発生し、3 つのセッションが次の処理を順番に実行します。
セッション 1:
START TRANSACTION; DELETE FROM t1 WHERE i = 1;
セッション 2:
START TRANSACTION; INSERT INTO t1 VALUES(1);
セッション 3:
START TRANSACTION; INSERT INTO t1 VALUES(1);
セッション 1:
COMMIT;
セッション 1 による最初の処理では、行の排他ロックが取得されます。セッション 2 と 3 の処理ではどちらも重複キーエラーが発生し、どちらのセッションも行の共有ロックを要求します。セッション 1 はコミット時に行の排他ロックを解放し、キュー内のセッション 2 と 3 の共有ロック要求が許可されます。この時点でセッション 2 と 3 でデッドロックが発生します。どちらも他方が保持している共有ロックのために、行の排他ロックを取得できません。
REPLACE
は、もし固有キーに衝突がなければ
INSERT
と同じように行われます。反対に、排他ネクストキーロックは更新されなければいけない行の上に置かれます。
INSERT INTO T SELECT ... FROM S
WHERE ...
は T
に挿入された各行に、ギャップロックなしの排他インデックスレコードロックを設定します。innodb_locks_unsafe_for_binlog
が有効であるかトランザクション遮断レベルが
READ
COMMITTED
である場合には、InnoDB
は S
での検索を一貫性読み取り (ロックなし)
として行います。それ以外の場合、InnoDB
は S
から取得した行に共有ネクストキーロックを設定します。InnoDB
は後者の場合にロックを設定する必要があります。バックアップからの前進復旧では、すべての
SQL
ステートメントはそれが元々行われたのとまったく同じ方法で実行されなければいけません。
CREATE
TABLE ... SELECT ...
は
INSERT
... SELECT
と同じく、SELECT
の実行を共有ネクストキーロックを使って行うか、あるいは一貫性読み取りとして行います。
REPLACE INTO T SELECT ... FROM S
WHERE ...
では、InnoDB
は
S
から取得した行に共有ネクストキーロックを設定します。
テーブル上であらかじめ指定された
AUTO_INCREMENT
カラムを初期化している間、InnoDB
は AUTO_INCREMENT
カラムと関係しているインデックスの最後に排他ロックを設定します。自動インクリメントカウンタにアクセスするとき、InnoDB
は、トランザクション全体の最後までではなく、現在の
SQL ステートメントの最後まで続く、特別な
AUTO-INC
テーブルロックモードを利用します。AUTO-INC
テーブルロックが保持されている間、ほかのセッションはテーブルへの挿入を行えません。項9.8. 「InnoDB
トランザクションモデルとロック」
を参照してください。
InnoDB
は、ロックを設定せずに、あらかじめ初期化された
AUTO_INCREMENT
カラムの値をフェッチします。
もし FOREIGN KEY
制約がテーブル上で定義されると、確認される制約条件を要求するすべての挿入、更新、または削除が、制約を確認するために参照するレコード上に共有レコードレベルロックを設定します。InnoDB
も、制約が失敗する場合に備えてこれらのロックを設定します。
LOCK TABLES
はテーブルロックを設定しますが、これはこれらのロックを設定する
InnoDB
レイヤより上位の MySQL
レイヤです。InnoDB
は innodb_table_locks =
1
(デフォルト) かつ
autocommit =
0
であればテーブルロックを認識しており、また
InnoDB
より上位の
MySQL レイヤは行レベルロックを識別します。
そうでなければ、InnoDB
の自動デッドロック検出は、そのようなテーブルロックが関連しているデッドロックを検出することはできません。また、この場合には上位の
MySQL
レイヤは行レベルロックを識別しないので、別のセッションが現在行レベルロックを持っているテーブル上にテーブルロックを得ることが可能です。しかし、項9.8.8. 「デッドロックの検出とロールバック」
で説明されているように、これはトランザクションインテグリティを危険にさらしたりはしません。項9.14. 「InnoDB
テーブル上の制約」
も参照してください。