一般に、ロック読み取り、UPDATE
、または DELETE
では、SQL ステートメントの処理時にスキャンされるすべてのインデックスレコード上に、レコードロックが設定されます。
行を除外する WHERE
条件がステートメント内に存在するかどうかは、関係ありません。InnoDB
には正確な WHERE
条件が記憶されませんが、スキャンされたインデックスの範囲は認識されます。通常、ロックはレコードの直前にある「ギャップ」への挿入もブロックするネクストキーロックです。ただし、ギャップロックは明示的に無効にすることができます。これにより、ネクストキーロックが使用されなくなります。詳細は、セクション14.2.6「InnoDB のレコード、ギャップ、およびネクストキーロック」を参照してください。トランザクション分離レベルによって、どのロックが設定されるのかも影響を受けます。セクション13.3.6「SET TRANSACTION 構文」を参照してください。
検索でセカンダリインデックスが使用され、設定されるインデックスレコードのロックが排他的である場合、InnoDB
は対応するクラスタ化されたインデックスレコードを取得し、それらにロックを設定することも行います。
共有ロックと排他ロックの違いについては、セクション14.2.3「InnoDB のロックモード」を参照してください。
ステートメントに適したインデックスがなく、MySQL がステートメントを処理するためにテーブル全体をスキャンする必要がある場合は、テーブルのすべての行がロックされます。その結果、そのテーブルへのほかのユーザーによるすべての挿入がブロックされます。クエリーで不必要に複数の行がスキャンされないように、適切なインデックスを作成することが重要です。
SELECT ... FOR UPDATE
または SELECT ... LOCK IN SHARE MODE
では、スキャンされた行についてはロックが取得され、WHERE
句に指定された条件を満たさないなどの理由で結果セットに含める対象から除外された行については、ロックが解放されることが予想されます。ただし場合によっては、クエリーの実行中に結果行とその元のソースとの関係が失われたために、行のロックがすぐに解除されない可能性もあります。たとえば UNION
では、スキャン (およびロック) されたテーブル内の行が、結果セットに含める対象となるかどうかの評価前に、一時テーブルに挿入される可能性があります。この状況では、一時テーブル内の行と元のテーブル内の行との関係は失われているため、クエリー実行が終了するまで後者の行のロックは解除されません。
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 でデッドロックが発生します。どちらも他方が保持している共有ロックのために、行の排他ロックを取得できません。
INSERT ... ON DUPLICATE KEY UPDATE
は、重複キーエラーが発生したときに、更新される行に共有ロックではなく、排他ネクストキーロックが配置されるという点で、単純なINSERT
と異なります。REPLACE
は、一意のキーが競合していなければ、INSERT
と同様に動作します。それ以外の場合は、置換される行に排他ネクストキーロックが配置されます。-
INSERT INTO T SELECT ... FROM S WHERE ...
は、T
に挿入された各行に、ギャップロックなしの排他インデックスレコードロックを設定します。トランザクション分離レベルがREAD COMMITTED
である場合、またはinnodb_locks_unsafe_for_binlog
が有効になっていて、トランザクション分離レベルがSERIALIZABLE
でない場合、InnoDB
は一貫性読み取り (ロックなし) としてS
上で検索を実行します。それ以外の場合、InnoDB
はS
から取得した行に共有ネクストキーロックを設定します。InnoDB
は、後者の場合にロックを設定する必要があります。バックアップからのロールフォワードリカバリ時には、すべての SQL ステートメントを元とまったく同じ方法で実行する必要があります。CREATE TABLE ... SELECT ...
は、INSERT ... SELECT
の場合と同様に、SELECT
を共有ネクストキーロックを使用して実行するか、一貫性読み取りとして実行します。構造文
REPLACE INTO t SELECT ... FROM s WHERE ...
またはUPDATE t ... WHERE col IN (SELECT ... FROM s ...)
でSELECT
が使用されると、InnoDB
はテーブルs
の行に共有ネクストキーロックを設定します。 -
InnoDB
は、テーブル上に事前に指定されたAUTO_INCREMENT
カラムの初期化中に、AUTO_INCREMENT
カラムに関連付けられたインデックスの最後に排他ロックを設定します。InnoDB
では、自動インクリメントカウンタにアクセスするときに、ロックがトランザクション全体の最後までではなく、現在の SQL ステートメントの最後まで続く、特別なAUTO-INC
テーブルロックモードが使用されます。AUTO-INC
テーブルロックが保持されている間は、ほかのセッションはそのテーブルに挿入できません。セクション14.2.2「InnoDB のトランザクションモデルおよびロック」を参照してください。InnoDB
は、ロックを設定せずに、事前に初期化されたAUTO_INCREMENT
カラムの値をフェッチします。 FOREIGN KEY
制約がテーブル上で定義されている場合は、制約条件をチェックする必要がある挿入、更新、または削除が行われると、制約をチェックするために、参照されるレコード上に共有レコードレベルロックが設定されます。InnoDB
は、制約が失敗する場合に備えて、これらのロックの設定も行います。-
LOCK TABLES
はテーブルロックを設定しますが、これらのロックを設定するInnoDB
レイヤーよりも上位の MySQL レイヤーです。InnoDB
は、innodb_table_locks = 1
(デフォルト) かつautocommit = 0
の場合にテーブルロックを認識し、InnoDB
よりも上位の MySQL レイヤーは、行レベルロックを識別します。それ以外の場合は、
InnoDB
の自動デッドロック検出では、このようなテーブルロックが関与するデッドロックを検出できません。また、この場合には上位の MySQL レイヤーは行レベルロックを識別しないため、現在別のセッションが行レベルロックを保持しているテーブル上でテーブルロックを取得できます。ただし、セクション14.2.10「デッドロックの検出とロールバック」で説明したように、これによりトランザクションの完全性が危険にさらされることはありません。セクション14.6.7「InnoDB テーブル上の制限」も参照してください。