7.6.9.1 锁定服务
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()
不实现命名空间,并且只提供独占锁,而不是独立的读写锁。
7.6.9.1.1 锁定服务C接口
本节描述如何使用锁定服务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 层面使用 Performance Schema 监控。详见Locking Service Monitoring。
7.6.9.1.2 锁定服务 Function Interface
本节描述了如何使用锁定服务接口提供的可加载函数。要使用C语言接口,请见第7.6.9.1.1节,“The Locking Service C Interface”。关于锁定服务接口的总体特征,请见第7.6.9.1节,“The Locking Service”。关于可加载函数的总体信息,请见添加可加载函数。
描述在第7.6.9.1.1节,“锁定服务C接口”中提到的锁定服务例程不需要安装,因为它们已经内置在服务器中。同样,不是所有映射到服务例程调用的可加载函数都可以不安装:这些函数必须在使用前安装。这一节描述了如何进行安装。关于可加载函数安装的总体信息,请见第7.7.1节,“安装和卸载可加载函数”。
锁定服务例程实现在名为plugin_ dir
系统变量指定的插件库文件中。文件基础名称是locking_service
。文件名后缀因平台而异(例如,.so
用于Unix和Unix-like系统,.dll
用于Windows)。
要安装锁定服务例程,请使用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);
在这个点上,如果您检查Performance Schema中的metadata_locks
表,您应该发现该会话持有六个不同的锁定,具有相同的(ns, lock1)
标识符。 (详见Locking Service Monitoring。)
由于该会话至少持有一次写锁定(ns, lock1)
,因此其他会话不能获取对其的读或写锁定。如果该会话仅持有该标识符的读锁定,其他会话可以获取对其的读锁定,但不能获取写锁定。
对于单个锁定获取调用,锁定是原子地获取的,但是原子性不适用于多个调用。因此,对于以下语句,whereservice_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 服务器元数据锁定框架实现的,因此可以通过 Performance Schema 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 接口实现了本节中描述的可加载函数。使用示例,请参见Locking Service Function Interface 使用。
这些函数共享以下特征:
-
返回值为非零,表示成功。否则,发生错误。
-
命名空间和锁名称必须是非
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()
获取的所有锁。没有锁在命名空间中的情况不是错误。