MySQL ディストリビューションは、次の 2 つのレベルでアクセスできるロックインタフェースを提供します:
SQL レベルでは、サービスルーチンへのコールにマップされる一連のユーザー定義関数 (UDF) として。
C 言語インタフェースとして、サーバープラグインまたはユーザー定義関数からプラグインサービスとして呼び出すことができます。
プラグインサービスの一般情報は、セクション5.6.8「MySQL プラグインサービス」 を参照してください。 ユーザー定義関数の一般情報は、Adding a Loadable Function を参照してください。
ロックインタフェースには、次の特性があります:
-
ロックには 3 つの属性があります: 名前空間、ロック名、およびロックモードをロックします:
ロックは、ネームスペースとロック名の組合せによって識別されます。 ネームスペースを使用すると、別々のネームスペースにロックを作成することで、異なるアプリケーションで同じロック名を競合せずに使用できます。 たとえば、アプリケーション A および B がそれぞれ
ns1
およびns2
のネームスペースを使用する場合、各アプリケーションは他のアプリケーションと干渉することなく、lock1
およびlock2
のロック名を使用できます。ロックモードは読取りまたは書込みのいずれかです。 読取りロックは共有されます: セッションに特定のロック識別子に対する読取りロックがある場合、他のセッションは同じ識別子に対する読取りロックを取得できます。 書込みロックは排他的です: セッションに特定のロック識別子に対する書込みロックがある場合、他のセッションは同じ識別子に対する読取りまたは書込みロックを取得できません。
ネームスペースおよびロック名は、
NULL
以外で空ではなく、最大 64 文字である必要があります。NULL
、空の文字列または 64 文字を超える文字列として指定されたネームスペースまたはロック名は、ER_LOCKING_SERVICE_WRONG_NAME
エラーになります。ロックインタフェースは名前空間とロック名をバイナリ文字列として扱うため、比較では大文字と小文字が区別されます。
ロックインタフェースは、ロックを取得してロックを解放する関数を提供します。 これらの関数を呼び出すために特別な権限は必要ありません。 権限チェックは、コール側アプリケーションの役割を果たします。
すぐに使用できない場合は、ロックを待機できます。 ロック取得コールには、ロックの取得を待機する秒数を示す整数のタイムアウト値が必要です。 ロックの取得に成功せずにタイムアウトに達すると、
ER_LOCKING_SERVICE_TIMEOUT
エラーが発生します。 タイムアウトが 0 の場合、待機はなく、ロックをすぐに取得できない場合はエラーが発生します。ロックインタフェースは、異なるセッションでのロック取得コール間のデッドロックを検出します。 この場合、ロックサービスは呼出し側を選択し、そのロック取得リクエストを
ER_LOCKING_SERVICE_DEADLOCK
エラーで終了します。 このエラーによってトランザクションがロールバックされることはありません。 デッドロックの場合にセッションを選択するには、ロックサービスは、書込みロックを保持するセッションよりも読取りロックを保持するセッションを優先します。セッションでは、単一のロック問合せコールを使用して複数のロックを取得できます。 特定のコールについて、ロック取得はアトミックです: すべてのロックが取得されると、コールは成功します。 ロックの取得に失敗した場合、呼出しはロックを取得せず、通常は
ER_LOCKING_SERVICE_TIMEOUT
またはER_LOCKING_SERVICE_DEADLOCK
エラーで失敗します。セッションは、同じロック識別子 (ネームスペースとロック名の組合せ) に対して複数のロックを取得できます。 これらのロックインスタンスは、読み取りロック、書き込みロック、またはその両方を組み合わせて使用できます。
セッション内で取得されたロックは、release-locks 関数を呼び出すことによって明示的に解放されるか、セッションの終了時に暗黙的に解放されます (通常または異常)。 トランザクションがコミットまたはロールバックされても、ロックは解放されません。
セッション内では、解放された特定のネームスペースに対するすべてのロックがまとめて解放されます。
ロックサービスによって提供されるインタフェースは、GET_LOCK()
および関連する SQL 関数によって提供されるインタフェースとは異なります (セクション12.15「ロック関数」 を参照)。 たとえば、GET_LOCK()
はネームスペースを実装せず、排他ロックのみを提供し、個別の読取りおよび書込みロックは提供しません。
このセクションでは、ロックサービスの C 言語インタフェースの使用方法について説明します。 かわりに UDF インタフェースを使用するには、セクション5.6.8.1.2「ロックサービス UDF インタフェース」 を参照してください。ロックサービスインタフェースの一般的な特性は、セクション5.6.8.1「ロックサービス」 を参照してください。 プラグインサービスの一般情報は、セクション5.6.8「MySQL プラグインサービス」 を参照してください。
ロックサービスを使用するソースファイルには、次のヘッダーファイルを含める必要があります:
#include <mysql/service_locking.h>
1 つ以上のロックを取得するには、次の関数を呼び出します:
int mysql_acquire_locking_service_locks(MYSQL_THD opaque_thd,
const char* lock_namespace,
const char**lock_names,
size_t lock_num,
enum enum_locking_service_lock_type lock_type,
unsigned long lock_timeout);
引数の意味は次のとおりです:
opaque_thd
: スレッドハンドル。NULL
として指定した場合は、現在のスレッドのハンドルが使用されます。lock_namespace
: ロックネームスペースを示す NULL で終わる文字列。lock_names
: 取得するロックの名前を提供する NULL 終了文字列の配列。lock_num
:lock_names
配列内の名前の数。lock_type
: 読取りロックまたは書込みロックを取得するためのロックモード (LOCKING_SERVICE_READ
またはLOCKING_SERVICE_WRITE
)。lock_timeout
: ロックの取得を待機してから中止する整数の秒数。
特定のネームスペースに対して取得したロックを解放するには、次の関数をコールします:
int mysql_release_locking_service_locks(MYSQL_THD opaque_thd,
const char* lock_namespace);
引数の意味は次のとおりです:
opaque_thd
: スレッドハンドル。NULL
として指定した場合は、現在のスレッドのハンドルが使用されます。lock_namespace
: ロックネームスペースを示す NULL で終わる文字列。
ロックサービスによって取得または待機されたロックは、パフォーマンススキーマを使用して SQL レベルで監視できます。 詳細は、ロックサービスの監視を参照してください。
このセクションでは、ロックサービスのユーザー定義関数 (UDF) インタフェースの使用方法について説明します。 かわりに C 言語インタフェースを使用するには、セクション5.6.8.1.1「ロックサービス C インタフェース」 を参照してください。ロックサービスインタフェースの一般的な特性は、セクション5.6.8.1「ロックサービス」 を参照してください。 ユーザー定義関数の一般情報は、Adding a Loadable Function を参照してください。
セクション5.6.8.1.1「ロックサービス C インタフェース」 で説明されているロックサービスルーチンはサーバーに組み込まれているため、インストールする必要はありません。 サービスルーチンへのコールにマップされるユーザー定義関数 (UDF) にも同じことは当てはまりません: UDF は使用する前にインストールする必要があります。 このセクションでは、その方法について説明します。 UDF のインストールに関する一般情報については、セクション5.7.1「ユーザー定義関数のインストールおよびアンインストール」 を参照してください。
ロックサービス UDF は、plugin_dir
システム変数で指定されたディレクトリにあるプラグインライブラリファイルに実装されます。 ファイルベース名は locking_service
です。 ファイル名の接尾辞は、プラットフォームごとに異なります (たとえば、.so
for Unix and Unix-like systems, .dll
for Windows)。
ロックサービス UDF をインストールするには、CREATE FUNCTION
ステートメントを使用して、プラットフォームの .so
接尾辞を必要に応じて調整します:
CREATE FUNCTION service_get_read_locks RETURNS INT
SONAME 'locking_service.so';
CREATE FUNCTION service_get_write_locks RETURNS INT
SONAME 'locking_service.so';
CREATE FUNCTION service_release_locks RETURNS INT
SONAME 'locking_service.so';
UDF がソースレプリケーションサーバーで使用されている場合は、レプリケーションの問題を回避するために、それらをすべてのレプリカサーバーにインストールします。
いったんインストールされると、UDF はアンインストールされるまでインストールされたままです。 これらを削除するには、DROP FUNCTION
ステートメントを使用します:
DROP FUNCTION service_get_read_locks;
DROP FUNCTION service_get_write_locks;
DROP FUNCTION service_release_locks;
ロックサービス UDF を使用する前に、UDF ロックインターフェイスのインストールまたはアンインストール で提供されている手順に従ってそれらをインストールします。
1 つ以上の読み取りロックを取得するには、次の関数を呼び出します:
mysql> SELECT service_get_read_locks('mynamespace', 'rlock1', 'rlock2', 10);
+---------------------------------------------------------------+
| service_get_read_locks('mynamespace', 'rlock1', 'rlock2', 10) |
+---------------------------------------------------------------+
| 1 |
+---------------------------------------------------------------+
最初の引数はロックネームスペースです。 最後の引数は、ロックの取得を待機する秒数を示す整数のタイムアウトです。 間の引数はロック名です。
前述の例では、関数はロック識別子が (mynamespace, rlock1)
および (mynamespace, rlock2)
のロックを取得します。
読み取りロックではなく書き込みロックを取得するには、次の関数を呼び出します:
mysql> SELECT service_get_write_locks('mynamespace', 'wlock1', 'wlock2', 10);
+----------------------------------------------------------------+
| service_get_write_locks('mynamespace', 'wlock1', 'wlock2', 10) |
+----------------------------------------------------------------+
| 1 |
+----------------------------------------------------------------+
この場合、ロック識別子は (mynamespace, wlock1)
および (mynamespace, wlock2)
です。
ネームスペースのすべてのロックを解除するには、次の関数を使用します:
mysql> SELECT service_release_locks('mynamespace');
+--------------------------------------+
| service_release_locks('mynamespace') |
+--------------------------------------+
| 1 |
+--------------------------------------+
各ロック関数は、成功した場合はゼロ以外を返します。 関数が失敗すると、エラーが発生します。 たとえば、ロック名は空にできないため、次のエラーが発生します:
mysql> SELECT service_get_read_locks('mynamespace', '', 10);
ERROR 3131 (42000): Incorrect locking service lock name ''.
セッションは、同じロック識別子に対して複数のロックを取得できます。 別のセッションに識別子の書込みロックがないかぎり、セッションは任意の数の読取りロックまたは書込みロックを取得できます。 識別子に対する各ロックリクエストは、新しいロックを取得します。 次のステートメントは、同じ識別子を持つ 3 つの書込みロックを取得してから、同じ識別子に対して 3 つの読取りロックを取得します:
SELECT service_get_write_locks('ns', 'lock1', 'lock1', 'lock1', 0);
SELECT service_get_read_locks('ns', 'lock1', 'lock1', 'lock1', 0);
この時点でパフォーマンススキーマ metadata_locks
テーブルを調べると、同じ (ns, lock1)
識別子を持つ 6 つの個別ロックがセッションに保持されていることがわかります。 (詳細は、ロックサービスの監視 を参照してください。)
セッションは (ns, lock1)
で少なくとも 1 つの書込みロックを保持するため、他のセッションは読取りまたは書込みのいずれのロックも取得できません。 セッションが識別子の読取りロックのみを保持している場合、他のセッションはその識別子の読取りロックを取得できますが、書込みロックは取得できません。
単一ロック取得コールのロックはアトミックに取得されますが、アトミック性はコール間で保持されません。 したがって、service_get_write_locks()
が結果セットの行ごとに 1 回コールされる次のようなステートメントの場合、アトミック性は個々のコールに対して保持されますが、ステートメント全体に対しては保持されません:
SELECT service_get_write_locks('ns', 'lock1', 'lock2', 0) FROM t1 WHERE ... ;
ロックサービスは、指定されたロック識別子に対する成功したリクエストごとに個別のロックを返すため、単一のステートメントが多数のロックを取得する可能性があります。 例:
INSERT INTO ... SELECT service_get_write_locks('ns', t1.col_name, 0) FROM t1;
これらのタイプのステートメントには、特定の悪影響がある場合があります。 たとえば、ステートメントが途中で失敗してロールバックされた場合、障害ポイントまで取得されたロックは引き続き存在します。 目的が、挿入された行と取得されたロックの間に対応するものである場合、その目的は満たされません。 また、ロックが特定の順序で付与されることが重要な場合は、オプティマイザが選択する実行計画によって結果セットの順序が異なる可能性があることに注意してください。 このような理由から、アプリケーションをステートメントごとに単一のロック取得コールに制限することをお薦めします。
ロックサービスは MySQL Server メタデータロックフレームワークを使用して実装されるため、パフォーマンススキーマ metadata_locks
テーブルを検査して、取得または待機したロックサービスロックをモニターします。
まず、メタデータロックインストゥルメントを有効にします:
mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES'
-> WHERE NAME = 'wait/lock/metadata/sql/mdl';
次に、いくつかのロックを取得し、metadata_locks
テーブルの内容を確認します:
mysql> SELECT service_get_write_locks('mynamespace', 'lock1', 0);
+----------------------------------------------------+
| service_get_write_locks('mynamespace', 'lock1', 0) |
+----------------------------------------------------+
| 1 |
+----------------------------------------------------+
mysql> SELECT service_get_read_locks('mynamespace', 'lock2', 0);
+---------------------------------------------------+
| service_get_read_locks('mynamespace', 'lock2', 0) |
+---------------------------------------------------+
| 1 |
+---------------------------------------------------+
mysql> SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS
-> FROM performance_schema.metadata_locks
-> WHERE OBJECT_TYPE = 'LOCKING SERVICE'\G
*************************** 1. row ***************************
OBJECT_TYPE: LOCKING SERVICE
OBJECT_SCHEMA: mynamespace
OBJECT_NAME: lock1
LOCK_TYPE: EXCLUSIVE
LOCK_STATUS: GRANTED
*************************** 2. row ***************************
OBJECT_TYPE: LOCKING SERVICE
OBJECT_SCHEMA: mynamespace
OBJECT_NAME: lock2
LOCK_TYPE: SHARED
LOCK_STATUS: GRANTED
ロックサービスロックの OBJECT_TYPE
値は LOCKING SERVICE
です。 これは、たとえば、USER LEVEL LOCK
の OBJECT_TYPE
を持つ GET_LOCK()
関数で取得されたロックとは異なります。
ロックネームスペース、名前およびモードは、OBJECT_SCHEMA
、OBJECT_NAME
および LOCK_TYPE
カラムに表示されます。 読取りロックおよび書込みロックには、それぞれ SHARED
および EXCLUSIVE
の LOCK_TYPE
値があります。
LOCK_STATUS
値は、取得したロックの場合は GRANTED
、待機中のロックの場合は PENDING
です。 あるセッションが書込みロックを保持していて、別のセッションが同じ識別子を持つロックを取得しようとすると、PENDING
が表示されます。
ロックサービスへの SQL インタフェースは、このセクションで説明するユーザー定義関数を実装します。 使用例については、UDF ロックインターフェイスの使用 を参照してください。
これらの関数は、次の特性を共有します:
成功の場合、戻り値はゼロ以外です。 それ以外の場合は、エラーが発生します。
ネームスペースおよびロック名は、
NULL
以外で空ではなく、最大 64 文字である必要があります。タイムアウト値は、エラーが発生するまでロックの取得を待機する秒数を示す整数である必要があります。 タイムアウトが 0 の場合、待機はなく、ロックをすぐに取得できないと、関数はエラーを生成します。
次のロックサービス UDF を使用できます:
-
service_get_read_locks(
namespace
,lock_name
[,lock_name
] ...,timeout
)指定されたタイムアウト値内にロックが取得されない場合、指定されたロック名を使用して、指定された名前空間内の 1 つ以上の読み取り (共有) ロックを取得し、エラーでタイムアウトします。
-
service_get_write_locks(
namespace
,lock_name
[,lock_name
] ...,timeout
)指定されたタイムアウト値内にロックが取得されない場合、指定されたロック名を使用して、指定されたネームスペース内の 1 つ以上の書込み (排他) ロックを取得し、エラーでタイムアウトします。
-
service_release_locks(
namespace
)指定されたネームスペースについて、
service_get_read_locks()
およびservice_get_write_locks()
を使用して現在のセッション内で取得されたすべてのロックを解放します。ネームスペースにロックがない場合は、エラーではありません。