MySQL 发行版提供了一个锁定接口,可以在两个级别访问:
-
在 SQL 级别上,以一组可加载函数的形式,每个函数映射到服务例程的调用。
-
作为 C 语言接口,从服务器插件或可加载函数中可调用。
有关插件服务的一般信息,请参阅 第 7.6.9 节,“MySQL 插件服务”。有关可加载函数的一般信息,请参阅 添加可加载函数。
锁定接口具有以下特征:
-
锁具有三个属性:锁命名空间、锁名称和锁模式:
-
锁通过命名空间和锁名称的组合来标识。命名空间使不同应用程序可以使用相同的锁名称,而不会冲突,因为它们在不同的命名空间中创建锁。例如,如果应用程序 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
错误。 -
一个会话可以获取同一个锁标识符(命名空间和锁名称的组合)的多个锁实例。这些锁实例可以是读锁、写锁或两者的混合。
-
会话获取的锁可以通过显式调用释放锁函数或在会话终止时(正常或异常)隐式释放。锁不会在事务提交或回滚时释放。
-
在一个会话中,对于给定的命名空间,所有锁一起释放。
锁定服务提供的接口与 GET_LOCK()
和相关 SQL 函数(见 第 14.14 节,“锁定函数”)提供的接口不同。例如,GET_LOCK()
不实现命名空间,并且只提供排他锁,而不是读锁和写锁。
本节描述如何使用锁定服务 C 语言接口。要使用函数接口,请参阅 第 7.6.9.1.2 节,“锁定服务函数接口” 锁定服务接口的一般特征,请参阅 第 7.6.9.1 节,“锁定服务”。关于插件服务的一般信息,请参阅 第 7.6.9 节,“MySQL 插件服务”。
使用锁定服务的源文件应包含以下头文件:
#include <mysql/service_locking.h>
要获取一个或多个锁,请调用以下函数:
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
:一个空终止的字符串,指示锁命名空间。 -
lock_names
:一个空终止字符串数组,提供要获取的锁的名称。 -
lock_num
:lock_names
数组中的名称数量。 -
lock_type
:锁模式,eitherLOCKING_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
:一个空终止的字符串,指示锁命名空间。
锁定服务监控可以在 SQL 级别使用性能模式监控。详细信息,请参阅 锁定服务监控。
本节描述如何使用锁定服务函数接口。要使用 C 语言接口,请参阅 第 7.6.9.1.1 节,“锁定服务 C 语言接口” 锁定服务接口的一般特征,请参阅 第 7.6.9.1 节,“锁定服务”。关于可加载函数的一般信息,请参阅 添加可加载函数。
锁定服务例程描述在 第 7.6.9.1.1 节,“锁定服务 C 语言接口” 中不需要安装,因为它们是内置于服务器中的。相比之下,映射到服务例程的可加载函数需要在使用前安装。本节描述如何安装它们。关于可加载函数安装的一般信息,请参阅 第 7.7.1 节,“安装和卸载可加载函数”。
锁定服务函数是在插件库文件中实现的,位于由plugin_dir
系统变量命名的目录中。文件的基本名称是locking_service
。文件名后缀因平台而异(例如,Unix 和 Unix-like 系统的 .so
,Windows 的 .dll
)。
要安装锁定服务函数,请使用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';
如果这些函数在复制源服务器上使用,请在所有副本服务器上安装它们,以避免复制问题。
一旦安装,这些函数将保持安装状态,直到卸载。要删除它们,请使用DROP FUNCTION
语句:
DROP FUNCTION service_get_read_locks;
DROP FUNCTION service_get_write_locks;
DROP FUNCTION service_release_locks;
在使用锁定服务函数之前,请按照安装或卸载锁定服务函数接口中的说明安装它们。
要获取一个或多个读锁,请调用此函数:
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 ''.
会话可以获取同一锁标识符的多个锁。只要其他会话没有写锁,该会话可以获取任意数量的读锁或写锁。每个锁请求都获取一个新的锁。以下语句获取三个写锁和三个读锁,所有锁的标识符相同:
SELECT service_get_write_locks('ns', 'lock1', 'lock1', 'lock1', 0);
SELECT service_get_read_locks('ns', 'lock1', 'lock1', 'lock1', 0);
如果您在这时检查性能架构 metadata_locks
表,您应该找到会话持有六个不同的锁,所有锁的标识符相同。(详见锁定服务监控。)
因为会话持有至少一个写锁 (ns, lock1)
,因此其他会话不能获取该锁,既不能读锁也不能写锁。如果会话只持有读锁,该锁的其他会话可以获取读锁,但不能获取写锁。
单个锁获取调用中的锁是原子的,但原子性不跨调用。因此,对于以下语句,其中 service_get_write_locks()
调用一次 per 结果集行,原子性适用于每个单独的调用,但不适用于整个语句:
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 服务器元数据锁框架实现,因此您可以通过检查性能架构 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
。这与例如,使用 GET_LOCK()
函数获取的锁不同,后者的 OBJECT_TYPE
为 USER LEVEL LOCK
。
锁命名空间、名称和模式出现在 OBJECT_SCHEMA
、OBJECT_NAME
和 LOCK_TYPE
列中。读锁和写锁的 LOCK_TYPE
值分别为 SHARED
和 EXCLUSIVE
。
锁状态 LOCK_STATUS
的值为 GRANTED
,表示锁已获取,PENDING
,表示锁正在等待。如果一个会话持有写锁,而另一个会话尝试获取同一标识符的锁,则可能会看到 PENDING
。
SQL 接口到锁定服务实现了本节中描述的可加载函数。有关使用示例,请参阅 使用锁定服务函数接口。
这些函数共享以下特征:
-
返回值非零表示成功,否则发生错误。
-
命名空间和锁名称必须为非
NULL
、非空且长度不超过 64 个字符。 -
超时值必须是整数,表示等待获取锁的秒数。如果超时为 0,则不等待,并在无法立即获取锁时产生错误。
以下是可用的锁定服务函数:
-
service_get_read_locks(
命名空间
,锁名称
[,锁名称
] ...,超时
)在给定的命名空间中获取一个或多个读锁(共享锁),使用给定的锁名称,并在给定的超时值内等待获取锁,如果锁未获取则产生错误。
-
service_get_write_locks(
命名空间
,锁名称
[,锁名称
] ...,超时
)在给定的命名空间中获取一个或多个写锁(排他锁),使用给定的锁名称,并在给定的超时值内等待获取锁,如果锁未获取则产生错误。
-
释放当前会话中在给定的命名空间中使用
service_get_read_locks()
和service_get_write_locks()
获取的所有锁。如果命名空间中没有锁,不会产生错误。