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
: 指示锁命名空间的以 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 级别进行监控。有关详细信息,请参见 锁定服务监控。
本节介绍如何使用其可加载函数提供的锁定服务接口。要使用 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 系统为 .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)
标识符的不同锁。(有关详细信息,请参见 锁定服务监控。)
由于该会话在 (ns, lock1)
上至少持有了一个写锁,因此没有其他会话可以为其获取锁,无论读锁还是写锁。如果该会话仅为标识符持有了读锁,则其他会话可以为其获取读锁,但不能获取写锁。
单个锁获取调用的锁是原子获取的,但原子性不跨调用保持。因此,对于以下语句,其中 service_get_write_locks()
对结果集的每一行调用一次,原子性对每个单独的调用有效,但对整个语句无效
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
,对于正在等待的锁,LOCK_STATUS
值为 PENDING
。如果您发现 PENDING
,则意味着一个会话持有一个写锁,而另一个会话正在尝试获取具有相同标识符的锁。
锁定服务的 SQL 接口实现了本节中描述的可加载函数。有关使用示例,请参见 使用锁定服务函数接口。
这些函数具有以下共同特征
返回值为非零表示成功。否则,将发生错误。
命名空间和锁名称必须为非
NULL
、非空且最大长度为 64 个字符。超时值必须是整数,指示在放弃获取锁之前等待获取锁的秒数。如果超时为 0,则不会等待,如果无法立即获取锁,则该函数会产生错误。
以下锁定服务函数可用
service_get_read_locks(
namespace
,lock_name
[,lock_name
] ...,timeout
)使用给定的锁名称在给定命名空间中获取一个或多个读(共享)锁,如果在给定超时值内未获取锁,则会超时并出现错误。
service_get_write_locks(
namespace
,lock_name
[,lock_name
] ...,timeout
)使用给定的锁名称在给定命名空间中获取一个或多个写(独占)锁,如果在给定超时值内未获取锁,则会超时并出现错误。
service_release_locks(
namespace
)对于给定的命名空间,释放当前会话中使用
service_get_read_locks()
和service_get_write_locks()
获取的所有锁。如果命名空间中没有锁,则不会发生错误。