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;
このステートメントは、
tbla
、tblc
およびtbld
でメタデータロックを順番に取得します (tbld
は名前順にtblc
に従うため): -
この若干異なるステートメントによって、
tbla
の名前が他の名前に変更され、tblc
の名前がtbla
に変更されます:RENAME TABLE tbla TO tblb, tblc TO tbla;
この場合、ステートメントは
tbla
、tblb
およびtblc
でメタデータロックを順番に取得します (tblb
はtblc
の前に名前順に付くため):
どちらのステートメントも、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;
このステートメントは、x
、x_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_x
、old_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;
サーバーはトランザクションが終了するまで、t
と nt
の両方に対するメタデータロックを保持します。 別のセッションがいずれかのテーブルに対して、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
が実行されるまで、クライアントの接続が切断され、サーバーが再起動されてもメタデータロックは維持されます。