このページは機械翻訳したものです。
一般に、ロック読み取り、UPDATE、または DELETE では、SQL ステートメントの処理時にスキャンされるすべてのインデックスレコード上に、レコードロックが設定されます。 行を除外する WHERE 条件がステートメント内に存在するかどうかは、関係ありません。 InnoDB には正確な WHERE 条件が記憶されませんが、スキャンされたインデックスの範囲は認識されます。 通常、ロックはレコードの直前にある「ギャップ」への挿入もブロックするネクストキーロックです。 ただし、ギャップロックは明示的に無効にすることができます。これにより、ネクストキーロックが使用されなくなります。 詳細は、セクション15.7.1「InnoDB ロック」を参照してください。 トランザクション分離レベルによって、どのロックが設定されるのかも影響を受けます。セクション15.7.2.1「トランザクション分離レベル」を参照してください。
検索でセカンダリインデックスが使用され、設定されるインデックスレコードのロックが排他的である場合、InnoDB は対応するクラスタ化されたインデックスレコードを取得し、それらにロックを設定することも行います。
ステートメントに適したインデックスがなく、MySQL がステートメントを処理するためにテーブル全体をスキャンする必要がある場合は、テーブルのすべての行がロックされます。その結果、そのテーブルへのほかのユーザーによるすべての挿入がブロックされます。 クエリーで不必要に複数の行がスキャンされないように、適切なインデックスを作成することが重要です。
InnoDB は、次のように特定のロックタイプを設定します。
SELECT ... FROMは一貫性読み取りであり、データベースのスナップショットを読み取り、トランザクションの分離レベルがSERIALIZABLEに設定されなければロックを設定しません。SERIALIZABLEレベルの場合、検索で見つかったインデックスレコード上に共有ネクストキーロックが設定されます。 ただし、一意の行を検索するために一意のインデックスを使用して行をロックするステートメントには、インデックスレコードのロックのみが必要です。一意インデックスを使用する
SELECT ... FOR UPDATEおよびSELECT ... FOR SHAREステートメントは、スキャンされた行のロックを取得し、結果セットに含まれない行のロックを解除します (たとえば、WHERE句で指定された基準を満たさない場合)。 ただし場合によっては、クエリーの実行中に結果行とその元のソースとの関係が失われたために、行のロックがすぐに解除されない可能性もあります。 たとえばUNIONでは、スキャン (およびロック) されたテーブル内の行が、結果セットに含める対象となるかどうかの評価前に、一時テーブルに挿入される可能性があります。 この状況では、一時テーブル内の行と元のテーブル内の行との関係は失われているため、クエリー実行が終了するまで後者の行のロックは解除されません。-
locking reads (
SELECTとFOR UPDATEまたはFOR SHARE)、UPDATEおよびDELETEステートメントの場合、実行されるロックは、ステートメントが一意の検索条件を持つ一意のインデックスを使用するか、範囲タイプの検索条件を使用するかによって異なります。一意の検索条件を使用した一意のインデックスの場合、
InnoDBは見つかったインデックスレコードのみをロックし、その前にあるギャップはロックしません。他の検索条件および一意でないインデックスの場合、
InnoDBは、gap locks または next-key locks を使用してスキャンされたインデックス範囲をロックし、他のセッションによる挿入を範囲の対象となるギャップにブロックします。 ギャップロックおよびネクストキーロックについては、セクション15.7.1「InnoDB ロック」 を参照してください。
検索で検出されたインデックスレコードの場合、
SELECT ... FOR UPDATEは、他のセッションによるSELECT ... FOR SHAREの実行または特定のトランザクション分離レベルでの読取りをブロックします。 一貫性読み取りでは、読み取られたビュー内に存在するレコードに設定されたロックはすべて無視されます。UPDATE ... WHERE ...は、検索で見つかったすべてのレコード上に排他ネクストキーロックを設定します。 ただし、一意の行を検索するために一意のインデックスを使用して行をロックするステートメントには、インデックスレコードのロックのみが必要です。UPDATEがクラスタ化されたインデックスレコードを変更すると、影響を受けるセカンダリインデックスレコードが暗黙的にロックされます。UPDATE操作では、新しいセカンダリインデックスレコードを挿入する前に重複チェックスキャンを実行するとき、および新しいセカンダリインデックスレコードを挿入するときに、影響を受けるセカンダリインデックスレコードの共有ロックも取得されます。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は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_autoinc_lock_mode=0では、InnoDBは特別なAUTO-INCテーブルロックモードを使用します。このモードでは、自動増分カウンタにアクセスしながら、ロックが取得され、(トランザクション全体の最後ではなく) 現在の SQL ステートメントの最後まで保持されます。AUTO-INCテーブルロックが保持されている間は、ほかのクライアントはそのテーブルに挿入できません。innodb_autoinc_lock_mode=1を使用した「「一括挿入」」でも同じ動作が発生します。 テーブルレベルのAUTO-INCロックは、innodb_autoinc_lock_mode=2では使用されません。 詳細は、セクション15.6.1.6「InnoDB での AUTO_INCREMENT 処理」 を参照してください。InnoDBは、ロックを設定せずに、事前に初期化されたAUTO_INCREMENTカラムの値をフェッチします。 FOREIGN KEY制約がテーブル上で定義されている場合は、制約条件をチェックする必要がある挿入、更新、または削除が行われると、制約をチェックするために、参照されるレコード上に共有レコードレベルロックが設定されます。InnoDBは、制約が失敗する場合に備えて、これらのロックの設定も行います。-
LOCK TABLESはテーブルロックを設定しますが、これらのロックを設定するInnoDBレイヤーよりも上位の MySQL レイヤーです。InnoDBは、innodb_table_locks = 1(デフォルト) かつautocommit = 0の場合にテーブルロックを認識し、InnoDBよりも上位の MySQL レイヤーは、行レベルロックを識別します。それ以外の場合は、
InnoDBの自動デッドロック検出では、このようなテーブルロックが関与するデッドロックを検出できません。 また、この場合には上位の MySQL レイヤーは行レベルロックを識別しないため、現在別のセッションが行レベルロックを保持しているテーブル上でテーブルロックを取得できます。 ただし、セクション15.7.5.2「デッドロック検出」で説明したように、これによりトランザクションの完全性が危険にさらされることはありません。 -
innodb_table_locks=1(デフォルト) の場合、LOCK TABLESで各テーブル上に 2 つのロックが取得されます。 MySQL レイヤーでのテーブルロックに加えて、InnoDBテーブルロックも取得されます。 バージョン 4.1.2 よりも前の MySQL では、InnoDBテーブルロックが取得されませんでした。この古い動作は、innodb_table_locks=0を設定すれば選択できます。InnoDBテーブルロックが取得されない場合は、テーブルの一部のレコードがほかのトランザクションによってロックされなくても、LOCK TABLESが完了します。MySQL 8.0 では、
LOCK TABLES ... WRITEを使用して明示的にロックされたテーブルには、innodb_table_locks=0が無効です。LOCK TABLES ... WRITEで暗黙的に (たとえば、トリガーを使用して)、またはLOCK TABLES ... READによって、読み取りまたは書き込み用にロックされたテーブルには有効です。 トランザクションで保持されているすべての
InnoDBロックは、トランザクションがコミットまたは中止されると解放されます。 したがって、autocommit=1モードのInnoDBテーブル上でLOCK TABLESを呼び出しても、取得されたInnoDBテーブルロックはすぐに解放されてしまうため、まったく意味がありません。LOCK TABLESでは暗黙的なCOMMITおよびUNLOCK TABLESが実行されるため、トランザクションの実行中に追加のテーブルをロックできません。