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


13.3.6 LOCK TABLES および UNLOCK TABLES ステートメント

LOCK TABLES
    tbl_name [[AS] alias] lock_type
    [, tbl_name [[AS] alias] lock_type] ...

lock_type: {
    READ [LOCAL]
  | [LOW_PRIORITY] WRITE
}

UNLOCK TABLES

MySQL では、クライアントセッションは、ほかのセッションと連携してテーブルにアクセスするために、またはそのセッションにテーブルへの排他的アクセスが必要な期間中はほかのセッションによってそのテーブルが変更されないようにするために、明示的にテーブルロックを取得できます。 セッションがロックを取得または解放できるのは、それ自体のためだけです。 あるセッションが別のセッションのためにロックを取得したり、別のセッションによって保持されているロックを解放したりすることはできません。

ロックを使用すると、トランザクションをエミュレートするか、またはテーブル更新時の速度を向上させることができます。 詳細は、テーブルロックの制限と条件 を参照してください。

LOCK TABLES は、現在のクライアントセッションのテーブルロックを明示的に取得します。 テーブルロックは、ベーステーブルまたはビューに対して取得できます。 ロックされる各オブジェクトに対する LOCK TABLES 権限と SELECT 権限が必要です。

ビューのロックの場合、LOCK TABLES は、そのビューで使用されているすべてのベーステーブルをロックされるテーブルのセットに追加し、それらのテーブルを自動的にロックします。 ロックされるビューの基礎となるテーブルの場合、LOCK TABLES は、ビュー定義者 (SQL SECURITY DEFINER ビューの場合) または実行者 (すべてのビューの場合) がテーブルに対する適切な権限を持っていることを確認します。

LOCK TABLES とトリガーで説明されているように、LOCK TABLES によって明示的にテーブルをロックした場合は、トリガーで使用されているテーブルもすべて暗黙的にロックされます。

LOCK TABLES を使用してテーブルを明示的にロックすると、外部キー制約に関連するテーブルが暗黙的にオープンおよびロックされます。 外部キーチェックでは、関連するテーブルに対して共有読取り専用ロック (LOCK TABLES READ) が取得されます。 カスケード更新では、操作に関連する関連テーブルに対してシェアードナッシング書込みロック (LOCK TABLES WRITE) が取得されます。

UNLOCK TABLES は、現在のセッションによって保持されているテーブルロックをすべて明示的に解放します。 LOCK TABLES は、新しいロックを取得する前に、現在のセッションによって保持されているテーブルロックをすべて暗黙的に解放します。

UNLOCK TABLES の別の使用法として、すべてのデータベース内のすべてのテーブルをロックできる FLUSH TABLES WITH READ LOCK ステートメントによって取得されたグローバルな読み取りロックの解放があります。 セクション13.7.8.3「FLUSH ステートメント」を参照してください。 (これは、特定時点のスナップショットを取得できる、Veritas などのファイルシステムがある場合にバックアップを取得するための非常に便利な方法です。)

テーブルロックは、ほかのセッションによる不適切な読み取りまたは書き込みからのみ保護します。 WRITE ロックを保持しているセッションは、DROP TABLETRUNCATE TABLE などのテーブルレベルの操作を実行できます。 READ ロックを保持しているセッションの場合、DROP TABLE および TRUNCATE TABLE 操作は許可されません。

次の説明は、TEMPORARY 以外のテーブルにのみ適用されます。 LOCK TABLESTEMPORARY テーブルに対して許可されます (ただし、無視されます)。 テーブルは、ほかのどのようなロックが有効になっているかには関係なく、そのテーブルが作成されたセッションから自由にアクセスできます。 ほかのどのセッションもそのテーブルを参照できないため、ロックは必要ありません。

テーブルロック取得

現在のセッション内でテーブルロックを取得するには、メタデータロックを取得する LOCK TABLES ステートメントを使用します (セクション8.11.4「メタデータのロック」 を参照)。

次のロックタイプを使用できます。

READ [LOCAL] ロック:

  • このロックを保持しているセッションは、テーブルを読み取ることができます (ただし、書き込みはできません)。

  • 複数のセッションが同時にテーブルに対する READ ロックを取得できます。

  • ほかのセッションは、READ ロックを明示的に取得することなく、テーブルを読み取ることができます。

  • LOCAL 修飾子を使用すると、ロックが保持されている間、ほかのセッションによる競合しない INSERT ステートメント (並列挿入) を実行できます。 (セクション8.11.3「同時挿入」を参照してください。) ただし、ロックを保持している間、サーバーの外部にあるプロセスを使用してデータベースを操作しようとしている場合は、READ LOCAL を使用できません。 InnoDB テーブルの場合、READ LOCALREAD と同じです。

[LOW_PRIORITY] WRITE ロック:

  • このロックを保持しているセッションは、テーブルの読み取りおよび書き込みが可能です。

  • このロックを保持しているセッションだけがテーブルにアクセスできます。 ロックが解放されるまで、ほかのどのセッションもアクセスできません。

  • WRITE ロックが保持されている間、テーブルに対するほかのセッションからのロック要求はブロックされます。

  • LOW_PRIORITY 修飾子は何の効果もありません。 以前のバージョンの MySQL では、ロックの動作に影響を与えましたが、これは当てはまらなくなっています。 これは非推奨になり、使用すると警告が生成されます。 代わりに、LOW_PRIORITY のない WRITE を使用してください。

WRITE ロックは通常、更新ができるだけ早く処理されるように、READ ロックより高い優先度を持っています。 つまり、あるセッションが READ ロックを取得したあと、別のセッションが WRITE ロックを要求した場合は、WRITE ロックを要求したセッションがロックを取得して解放するまで、以降の READ ロック要求が待たされます。 (このポリシーの例外は、max_write_lock_count システム変数の小さい値に対して発生する可能性があります。セクション8.11.4「メタデータのロック」 を参照してください。)

LOCK TABLES ステートメントが、いずれかのテーブルに対するほかのセッションによって保持されているロックのために待機する必要がある場合、このステートメントはすべてのロックを取得できるまでブロックされます。

ロックが必要なセッションは、必要なすべてのロックを 1 つの LOCK TABLES ステートメントで取得する必要があります。 このように取得されたロックが保持されている間、このセッションは、ロックされたテーブルにのみアクセスできます。 たとえば、次のステートメントシーケンスでは、t2LOCK TABLES ステートメントでロックされていないため、このテーブルにアクセスしようとするとエラーが発生します。

mysql> LOCK TABLES t1 READ;
mysql> SELECT COUNT(*) FROM t1;
+----------+
| COUNT(*) |
+----------+
|        3 |
+----------+
mysql> SELECT COUNT(*) FROM t2;
ERROR 1100 (HY000): Table 't2' was not locked with LOCK TABLES

INFORMATION_SCHEMA データベース内のテーブルは例外です。 これらのテーブルは、セッションが LOCK TABLES によって取得されたテーブルロックを保持している間であっても、明示的にロックされることなくアクセスできます。

ロックされたテーブルを、同じ名前を使用して 1 つのクエリーで複数回参照することはできません。 代わりにエイリアスを使用し、そのテーブルと各エイリアスのための個別のロックを取得します。

mysql> LOCK TABLE t WRITE, t AS t1 READ;
mysql> INSERT INTO t SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> INSERT INTO t SELECT * FROM t AS t1;

最初の INSERT では、ロックされたテーブルに対する同じ名前への参照が 2 つ存在するため、エラーが発生します。 2 番目の INSERT は、テーブルへの参照で異なる名前が使用されるため、成功します。

ステートメントがエイリアスを使用してテーブルを参照する場合は、その同じエイリアスを使用してテーブルをロックする必要があります。 エイリアスを指定しないでテーブルをロックすることはできません。

mysql> LOCK TABLE t READ;
mysql> SELECT * FROM t AS myalias;
ERROR 1100: Table 'myalias' was not locked with LOCK TABLES

逆に、エイリアスを使用してテーブルをロックする場合は、ステートメント内でそのエイリアスを使用してテーブルを参照する必要があります。

mysql> LOCK TABLE t AS myalias READ;
mysql> SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> SELECT * FROM t AS myalias;

テーブルロック解除

セッションによって保持されているテーブルロックが解放される場合は、すべてのテーブルロックが一度に解放されます。 セッションは明示的にロックを解放できます。また、特定の状況で、ロックが暗黙的に解放される場合もあります。

  • セッションは、UNLOCK TABLES によって明示的にロックを解放できます。

  • セッションがすでにロックを保持している間にロックを取得するために LOCK TABLES ステートメントを発行した場合は、新しいロックが付与される前に、その既存のロックが暗黙的に解放されます。

  • セッションが (たとえば、START TRANSACTION で) トランザクションを開始した場合は、暗黙的な UNLOCK TABLES が実行され、既存のロックが解放されます。 (テーブルロックとトランザクションの間の通信の詳細は、テーブルロックとトランザクションの通信を参照してください。)

クライアントセッションの接続が (正常または異常にかかわらず) 終了した場合、サーバーは、そのセッションによって保持されているすべてのテーブルロック (トランザクションおよび非トランザクション) を暗黙的に解放します。 クライアントが再接続すると、ロックは無効になります。 さらに、クライアントにアクティブなトランザクションがある場合、サーバーは切断時にそのトランザクションをロールバックし、再接続が発生した場合は、自動コミットが有効になった状態で新しいセッションが開始されます。 このため、クライアントは自動再接続を無効にすることが必要になる場合があります。 自動再接続が有効な場合、再接続が発生してもテーブルロックまたは現在のトランザクションが失われても、クライアントには通知されません。 自動再接続が無効になっている場合は、接続が削除されると、発行された次のステートメントに対してエラーが発生します。 クライアントはそのエラーを検出し、ロックの再取得やトランザクションの再実行などの適切なアクションを実行できます。 Automatic Reconnection Controlを参照してください。

注記

ロックされたテーブル上で ALTER TABLE を使用すると、そのテーブルがロック解除される場合があります。 たとえば、2 番目の ALTER TABLE 操作を試みると、エラー「テーブル 'tbl_name' は LOCK TABLES でロックされていません」が発生する場合があります。 これに対処するには、2 番目の変更の前にテーブルを再度ロックします。 セクションB.3.6.1「ALTER TABLE での問題」も参照してください。

テーブルロックとトランザクションの通信

LOCK TABLES および UNLOCK TABLES は、トランザクションの使用との間で次のように通信します。

  • LOCK TABLES はトランザクションセーフではないため、テーブルをロックしようとする前に、アクティブなトランザクションをすべて暗黙的にコミットします。

  • UNLOCK TABLES は、アクティブなトランザクションをすべて暗黙的にコミットしますが、これが行われるのは、テーブルロックを取得するために LOCK TABLES が使用された場合のみです。 たとえば、次の一連のステートメントでは、UNLOCK TABLES がグローバルな読み取りロックを解放しますが、有効なテーブルロックがないためにトランザクションはコミットされません。

    FLUSH TABLES WITH READ LOCK;
    START TRANSACTION;
    SELECT ... ;
    UNLOCK TABLES;
  • トランザクションを (たとえば、START TRANSACTION で) 開始すると、現在のトランザクションはすべて暗黙的にコミットされ、既存のテーブルロックが解放されます。

  • FLUSH TABLES WITH READ LOCK は、グローバルな読み取りロックを取得しますが、テーブルロックは取得しないため、テーブルロックと暗黙的なコミットに関して LOCK TABLES および UNLOCK TABLES と同じ動作には従いません。 たとえば、START TRANSACTION は、グローバルな読み取りロックを解放しません。 セクション13.7.8.3「FLUSH ステートメント」を参照してください。

  • 暗黙的にトランザクションのコミットを発生させるその他のステートメントは、既存のテーブルロックを解放しません。 このようなステートメントのリストについては、セクション13.3.3「暗黙的なコミットを発生させるステートメント」を参照してください。

  • トランザクションテーブル (InnoDB テーブルなど) で LOCK TABLES および UNLOCK TABLES を使用するための正しい方法は、SET autocommit = 0 (START TRANSACTION ではなく) に続けて LOCK TABLES を指定することによってトランザクションを開始し、そのトランザクションを明示的にコミットするまで UNLOCK TABLES を呼び出さないことです。 たとえば、テーブル t1 に書き込み、テーブル t2 から読み取る必要がある場合は、次のように実行できます。

    SET autocommit=0;
    LOCK TABLES t1 WRITE, t2 READ, ...;
    ... do something with tables t1 and t2 here ...
    COMMIT;
    UNLOCK TABLES;

    LOCK TABLES を呼び出すと、InnoDB は内部的に独自のテーブルロックを取得し、MySQL は独自のテーブルロックを取得します。 InnoDB は次のコミット時に内部のテーブルロックを解放しますが、MySQL でテーブルロックが解放されるようにするには、UNLOCK TABLES を呼び出す必要があります。 autocommit = 1 を指定すると、LOCK TABLES の呼び出しの直後に InnoDB によって内部のテーブルロックが解放され、デッドロックが非常に発生しやすくなる場合があるため、この指定は行わないようにしてください。 autocommit = 1 が指定された場合、古いアプリケーションが不必要なデッドロックを回避するのに役立つように、InnoDB は内部のテーブルロックをまったく取得しません。

  • ROLLBACK は、テーブルロックを解放しません。

LOCK TABLES とトリガー

LOCK TABLES によって明示的にテーブルをロックした場合は、トリガーで使用されているテーブルもすべて暗黙的にロックされます。

  • これらのロックは、LOCK TABLES ステートメントによって明示的に取得されるロックと同時に取得されます。

  • トリガーで使用されているテーブルに対するロックは、そのテーブルが読み取りのみに使用されているかどうかによって異なります。 読み取りのみに使用されている場合は、読み取りロックで十分です。 そうでない場合は、書き込みロックが使用されます。

  • テーブルが LOCK TABLES によって読み取りに対して明示的にロックされているが、トリガー内で変更される可能性があるために書き込みに対してロックする必要がある場合は、読み取りロックではなく書き込みロックが取得されます。 (つまり、トリガー内でのテーブルの表示のために必要な暗黙の書き込みロックによって、テーブルに対する明示的な読み取りロック要求が書き込みロック要求に変換されます。)

次のステートメントを使用して、2 つのテーブル t1t2 をロックするとします。

LOCK TABLES t1 WRITE, t2 READ;

t1 または t2 にトリガーがある場合、トリガー内で使用されるテーブルもロックされます。 t1 に、次のように定義されたトリガーが含まれているとします。

CREATE TRIGGER t1_a_ins AFTER INSERT ON t1 FOR EACH ROW
BEGIN
  UPDATE t4 SET count = count+1
      WHERE id = NEW.id AND EXISTS (SELECT a FROM t3);
  INSERT INTO t2 VALUES(1, 2);
END;

LOCK TABLES ステートメントの結果として、t1t2 は、このステートメントに現れるためにロックされます。また、t3t4 は、トリガー内で使用されているためにロックされます。

  • t1 は、WRITE ロック要求ごとに、書き込みに対してロックされます。

  • t2 は、要求が READ ロックに対するものであったとしても、書き込みに対してロックされます。 これは、トリガー内で t2 に挿入されるために発生します。したがって、READ 要求は WRITE 要求に変換されます。

  • t3 は、トリガー内から読み取られるだけであるため、読み取りに対してロックされます。

  • t4 は、トリガー内で更新される可能性があるため、書き込みに対してロックされます。

テーブルロックの制限と条件

テーブルロックを待機しているセッションを終了するために、KILL を安全に使用できます。 セクション13.7.8.4「KILL ステートメント」を参照してください。

LOCK TABLES および UNLOCK TABLES は、ストアドプログラム内では使用できません。

performance_schema データベース内のテーブルは、setup_xxx テーブルを除き、LOCK TABLES ではロックできません。

LOCK TABLES ステートメントが有効になっている間、次のステートメントは禁止されます。CREATE TABLECREATE TABLE ... LIKECREATE VIEWDROP VIEW、およびストアドファンクション、ストアドプロシージャー、イベントでの DDL ステートメント。

一部の操作では、mysql データベース内のシステムテーブルにアクセスする必要があります。 たとえば、HELP ステートメントにはサーバー側のヘルプテーブルの内容が必要であり、また CONVERT_TZ() はタイムゾーンテーブルの読み取りが必要になる可能性があります。 サーバーは、ユーザーが明示的にロックしなくても済むように、必要に応じてシステムテーブルを読み取りに対して暗黙的にロックします。 次のテーブルは、今説明したように処理されます。

mysql.help_category
mysql.help_keyword
mysql.help_relation
mysql.help_topic
mysql.time_zone
mysql.time_zone_leap_second
mysql.time_zone_name
mysql.time_zone_transition
mysql.time_zone_transition_type

これらのテーブルのいずれかに対する WRITE ロックを LOCK TABLES ステートメントで明示的に設定する場合は、そのテーブルがロックされる唯一のテーブルである必要があります。ほかのどのテーブルも、同じステートメントではロックできません。

1 つの UPDATE ステートメントはすべてアトミックであるため、通常、テーブルをロックする必要はありません。現在実行中の SQL ステートメントを、ほかのどのセッションも妨げることはできません。 ただし、テーブルのロックによって利点が得られる可能性のある場合がいくつかあります。

  • 一連の MyISAM テーブルに対して多くの操作を実行しようとしている場合は、使用しようとしているテーブルをロックする方がはるかに高速です。 MyISAM テーブルをロックすると、MySQL はロックされたテーブルのキーキャッシュを UNLOCK TABLES が呼び出されるまでフラッシュしないため、そのテーブルに対する挿入、更新、または削除が高速化されます。 通常、キーキャッシュは各 SQL ステートメントのあとでフラッシュされます。

    テーブルロックのマイナス面は、READ によってロックされたテーブルをどのセッションも更新できず (ロックを保持しているセッションを含む)、ロックを保持しているセッションを除き、WRITE によってロックされたテーブルにどのセッションもアクセスできない点です。

  • 非トランザクションストレージエンジンに対してテーブルを使用している場合、SELECTUPDATE の間にテーブルがほかのセッションによって変更されないようにするには、LOCK TABLES を使用する必要があります。 次に示す例では、安全に実行するために LOCK TABLES が必要です。

    LOCK TABLES trans READ, customer WRITE;
    SELECT SUM(value) FROM trans WHERE customer_id=some_id;
    UPDATE customer
      SET total_value=sum_from_previous_statement
      WHERE customer_id=some_id;
    UNLOCK TABLES;

    LOCK TABLES を使用しない場合は、SELECT ステートメントと UPDATE ステートメントの実行の間に、別のセッションによって trans テーブルに新しい行が挿入される可能性があります。

多くの場合は、相対的な更新 (UPDATE customer SET value=value+new_value) または LAST_INSERT_ID() 関数を使用することによって LOCK TABLES の使用を回避できます。

場合によっては、ユーザーレベルのアドバイザリロック関数 GET_LOCK() および RELEASE_LOCK() を使用してテーブルのロックを回避することもできます。 高速化のために、これらのロックはサーバーのハッシュテーブル内に保存され、pthread_mutex_lock()pthread_mutex_unlock() で実装されます。 セクション12.15「ロック関数」を参照してください。

ロックポリシーの詳細は、セクション8.11.1「内部ロック方法」を参照してください。


関連キーワード:  ステートメント, ロック, テーブル, TABLES, LOCK, セッション, TABLE, 取得, CREATE, トランザクション