MySQL 8.0 リファレンスマニュアル


MySQL 8.0 リファレンスマニュアル  /  ...  /  メタデータのロック

8.11.4 メタデータのロック

MySQL では、メタデータロックを使用して、データベースオブジェクトへの同時アクセスを管理し、データの一貫性を確保します。 メタデータのロックは、テーブルのみでなく、スキーマ、ストアドプログラム (プロシージャ、ファンクション、トリガー、スケジュール済イベント)、テーブルスペース、GET_LOCK() 関数で取得されたユーザーロック (セクション12.15「ロック関数」 を参照)、および セクション5.6.8.1「ロックサービス」 で説明されているロックサービスで取得されたロックにも適用されます。

パフォーマンススキーマ metadata_locks テーブルは、メタデータロック情報を公開します。この情報は、ロックを保持しているセッションや、ロックを待機してブロックされているセッションなどを確認する場合に役立ちます。 詳細は、セクション27.12.13.3「metadata_locks テーブル」を参照してください。

メタデータロックには多少のオーバーヘッドが伴い、クエリーボリュームが増加するにつれて増加します。 複数のクエリーが同じオブジェクトにアクセスを試みることが多くなるほど、メタデータの競合が増加します。

メタデータのロックは、テーブル定義キャッシュの代替ではなく、その相互排他ロックとロックは、LOCK_open 相互排他ロックと異なります。 次の説明では、メタデータのロックの仕組みに関する情報を提供します。

メタデータロックの取得

特定のロックに複数の待機者がいる場合は、max_write_lock_count システム変数に関連する例外を除いて、優先度の高いロックリクエストが最初に満たされます。 書き込みロック要求の優先順位は、読み取りロック要求よりも高くなります。 ただし、max_write_lock_count がある程度低い値 (たとえば、10) に設定されている場合、読み取りロック要求がすでに 10 個の書き込みロック要求を優先して渡されていれば、保留中の書き込みロック要求よりも読み取りロック要求が優先されることがあります。 通常、max_write_lock_count のデフォルト値は非常に大きいため、この動作は発生しません。

ステートメントは、同時にではなくメタデータロックを 1 つずつ取得し、プロセスでデッドロック検出を実行します。

DML ステートメントは、通常、ステートメントで記述されているテーブルの順序でロックを取得します。

DDL ステートメント、LOCK TABLES およびその他の類似するステートメントは、明示的に指定されたテーブルに対するロックを名前順に取得することで、同時 DDL ステートメント間のデッドロックの可能性のある数を減らします。 暗黙的に使用されるテーブル (ロックする必要がある外部キー関係のテーブルなど) では、ロックが異なる順序で取得される場合があります。

たとえば、RENAME TABLE は、名前順にロックを取得する DDL ステートメントです:

  • 次の RENAME TABLE ステートメントは、tbla の名前を他の名前に変更し、tblc の名前を tbla に変更します:

    RENAME TABLE tbla TO tbld, tblc TO tbla;

    このステートメントは、tblatblc および tbld でメタデータロックを順番に取得します (tbld は名前順に tblc に従うため):

  • この若干異なるステートメントによって、tbla の名前が他の名前に変更され、tblc の名前が tbla に変更されます:

    RENAME TABLE tbla TO tblb, tblc TO tbla;

    この場合、ステートメントは tblatblb および tblc でメタデータロックを順番に取得します (tblbtblc の前に名前順に付くため):

どちらのステートメントも、tbla および tblc のロックをこの順序で取得しますが、残りのテーブル名のロックが tblc の前と後のどちらで取得されるかが異なります。

メタデータロックの取得順序は、次の例に示すように、複数のトランザクションが同時に実行される場合に操作結果に違いが生じる可能性があります。

同じ構造を持つ 2 つのテーブル x および x_new から開始します。 次の 3 つのクライアントが、これらのテーブルを含むステートメントを発行します:

クライアント 1:

LOCK TABLE x WRITE, x_new WRITE;

このステートメントは、x および x_new で名前順に書込みロックを要求および取得します。

クライアント 2:

INSERT INTO x VALUES(1);

ステートメントは、x で書込みロックを待機していることをリクエストおよびブロックします。

クライアント 3:

RENAME TABLE x TO x_old, x_new TO x;

このステートメントは、xx_new および x_old で排他ロックを名前順に要求しますが、x でのロックの待機をブロックします。

クライアント 1:

UNLOCK TABLES;

このステートメントは、x および x_new の書込みロックを解放します。 クライアント 3 による x の排他ロックリクエストは、クライアント 2 による書込みロックリクエストよりも優先度が高いため、クライアント 3 は x でロックを取得し、x_new および x_old でも名前変更を実行してロックを解放します。 次に、クライアント 2 は x でロックを取得し、挿入を実行してロックを解放します。

ロック取得順序により、INSERT の前に RENAME TABLE が実行されます。 挿入が行われる x は、クライアント 2 が挿入を発行し、クライアント 3 によって x に名前が変更されたときに x_new という名前のテーブルです:

mysql> SELECT * FROM x;
+------+
| i    |
+------+
|    1 |
+------+

mysql> SELECT * FROM x_old;
Empty set (0.01 sec)

かわりに、同じ構造を持つ x および new_x という名前のテーブルから始めます。 ここでも、3 つのクライアントが、次のテーブルを含むステートメントを発行します:

クライアント 1:

LOCK TABLE x WRITE, new_x WRITE;

このステートメントは、new_x および x で名前順に書込みロックを要求および取得します。

クライアント 2:

INSERT INTO x VALUES(1);

ステートメントは、x で書込みロックを待機していることをリクエストおよびブロックします。

クライアント 3:

RENAME TABLE x TO old_x, new_x TO x;

このステートメントは、new_xold_x および x で排他ロックを名前順に要求しますが、new_x でのロックの待機をブロックします。

クライアント 1:

UNLOCK TABLES;

このステートメントは、x および new_x の書込みロックを解放します。 x の場合、保留中のリクエストはクライアント 2 のみであるため、クライアント 2 はロックを取得し、挿入を実行してロックを解放します。 new_x の場合、保留中のリクエストはクライアント 3 のみで、クライアント 3 はそのロックを取得できます (old_x でのロックも取得できます)。 名前変更操作は、クライアント 2 の挿入が終了してロックを解放するまで、x でのロックに対してブロックされます。 次に、クライアント 3 は x でロックを取得し、名前変更を実行してロックを解放します。

この場合、ロック取得順序により、RENAME TABLE の前に INSERT が実行されます。 挿入先の x は元の x で、名前変更操作によって old_x に名前が変更されました:

mysql> SELECT * FROM x;
Empty set (0.01 sec)

mysql> SELECT * FROM old_x;
+------+
| i    |
+------+
|    1 |
+------+

前述の例のように、同時ステートメントでのロック取得の順序によって操作結果のアプリケーションが異なる場合は、ロック取得の順序に影響を与えるようにテーブル名を調整できます。

メタデータロックは、必要に応じて外部キー制約によって関連付けられたテーブルに拡張され、DML 操作と DDL 操作の競合が関連するテーブルで同時に実行されないようにします。 親テーブルを更新すると、外部キーメタデータの更新中に子テーブルのメタデータロックが取得されます。 外部キーメタデータは子テーブルによって所有されます。

メタデータロックの解放

トランザクションのシリアライザビリティーを確保するため、サーバーは、別のセッションで、未完了の明示的または暗黙的に開始されたトランザクションで使用されているテーブルに対して、セッションがデータ定義言語 (DDL) ステートメントを実行することを許可してはいけません。 サーバーは、トランザクション内で使用されているテーブルに対してメタデータロックを獲得し、トランザクションが終了するまでそれらのロックの解放を延期させることによって、これを実現します。 テーブルへのメタデータロックは、テーブルの構造への変更を妨げます。 このロックアプローチには、あるセッション内のトランザクションによって使用されているテーブルは、トランザクションが終了するまで、ほかのセッションによって DDL ステートメントで使用できないという問題があります。

この原則は、トランザクションテーブルだけでなく、非トランザクションテーブルにも適用されます。 あるセッションがトランザクションテーブル t と非トランザクションテーブル nt を次のように使用するトランザクションを開始するとします。

START TRANSACTION;
SELECT * FROM t;
SELECT * FROM nt;

サーバーはトランザクションが終了するまで、tnt の両方に対するメタデータロックを保持します。 別のセッションがいずれかのテーブルに対して、DDL または書き込みロック操作を試みると、それはトランザクションの終了時にメタデータロックが解放されるまでブロックされます。 たとえば、2 つめのセッションはこれらのいずれかの操作を試みるとブロックされます。

DROP TABLE t;
ALTER TABLE t ...;
DROP TABLE nt;
ALTER TABLE nt ...;
LOCK TABLE t ... WRITE;

LOCK TABLES ... READ にも同じ動作が適用されます。 つまり、テーブル (トランザクションまたは非トランザクション) ブロックを更新し、そのテーブルに対して LOCK TABLES ... READ によってブロックされる明示的または暗黙的に開始されたトランザクションです。

サーバーが構文上有効であるが、実行中に失敗するステートメントのメタデータロックを獲得した場合、そのロックを早期に解放しません。 失敗したステートメントがバイナリログに書き込まれ、ロックによってログの一貫性が保護されるため、ロックの解放はまだトランザクションの終了まで延期されます。

自動コミットモードでは、各ステートメントが事実上完全なトランザクションであるため、そのステートメントに対して獲得されたメタデータロックは、ステートメントの終了までしか保持されません。

PREPARE ステートメント中に獲得されたメタデータロックは、準備が複数ステートメントトランザクション内で行われる場合でも、ステートメントが準備されると解放されます。

MySQL 8.0.13 では、PREPARED 状態の XA トランザクションの場合、XA COMMIT または XA ROLLBACK が実行されるまで、クライアントの接続が切断され、サーバーが再起動されてもメタデータロックは維持されます。


関連キーワード:  ロック, テーブル, ステートメント, 取得, メタデータ, トランザクション, 名前, インデックス, InnoDB, TABLE