MySQL 发行版提供了一个可在两个级别访问的锁定接口:
在 SQL 级别,作为一组可加载函数,每个函数都映射到对服务例程的调用。
作为 C 语言接口,可作为服务器插件或可加载函数的插件服务调用。
有关插件服务的一般信息,请参阅 第 5.5.6 节,“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
orER_LOCKING_SERVICE_DEADLOCK
错误。一个会话可以为同一个锁标识符(名称空间和锁名称的组合)获取多个锁。这些锁实例可以是读锁、写锁或两者的混合。
在会话中获取的锁通过调用释放锁函数显式释放,或者在会话终止时(正常或异常)隐式释放。事务提交或回滚时不会释放锁。
在会话中,给定名称空间的所有锁在释放时一起释放。
锁定服务提供的接口GET_LOCK()
与相关 SQL 函数提供的接口不同(请参阅第 12.15 节,“锁定函数”)。例如,GET_LOCK()
不实现命名空间,只提供独占锁,不提供读写锁。
本节介绍如何使用锁定服务 C 语言接口。要改用函数接口,请参阅 第 5.5.6.1.2 节,“锁定服务函数接口”有关锁定服务接口的一般特征 ,请参阅第 5.5.6.1节,“锁定服务”。有关插件服务的一般信息,请参阅第 5.5.6 节,“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
:一个空终止字符串数组,提供要获取的锁的名称。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 语言接口,请参阅 第 5.5.6.1.1 节,“锁定服务 C 接口”有关锁定服务接口的一般特征 ,请参阅第 5.5.6.1节,“锁定服务”。有关可加载函数的一般信息,请参阅 添加可加载函数。
无需安装第 5.5.6.1.1 节“锁定服务 C 接口” 中描述的锁定服务例程 ,因为它们内置于服务器中。对于映射到对服务例程的调用的可加载函数,情况并非如此:这些函数必须在使用前安装。本节描述如何做到这一点。有关可加载函数安装的一般信息,请参阅 第 5.6.1 节,“安装和卸载可加载函数”。
锁定服务功能在位于
plugin_dir
系统变量命名的目录中的插件库文件中实现。文件基名是locking_service
. 文件名后缀因平台而异(例如,
.so
对于 Unix 和类 Unix 系统,
.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);
如果
metadata_locks
此时检查 Performance Schema 表,您会发现该会话持有六个具有相同
(ns, lock1)
标识符的不同锁。(详见
锁定服务监控。)
因为该会话在 上持有至少一个写锁
(ns, lock1)
,所以没有其他会话可以为其获取锁,无论是读锁还是写锁。如果会话只持有标识符的读锁,则其他会话可以为其获取读锁,但不能获取写锁。
单个锁获取调用的锁是原子获取的,但原子性并不适用于所有调用。因此,对于如下语句,where
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;
这些类型的陈述可能会产生某些不利影响。例如,如果语句中途失败并回滚,则在失败点之前获取的锁仍然存在。如果意图是在插入的行和获取的锁之间建立对应关系,则不满足该意图。此外,如果按特定顺序授予锁很重要,请注意结果集顺序可能因优化器选择的执行计划而异。由于这些原因,最好将应用程序限制为每个语句调用一次锁获取。
metadata_locks
锁定服务是使用 MySQL 服务器元数据锁框架实现的,因此您可以通过检查 Performance Schema表
来监控获取或等待的锁定服务锁。
首先,启用元数据锁定工具:
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
of USER
LEVEL LOCK
。
The lock namespace, name, and mode appear in the
OBJECT_SCHEMA
,
OBJECT_NAME
, and
LOCK_TYPE
columns. Read and write locks
have LOCK_TYPE
values of
SHARED
and EXCLUSIVE
,
respectively.
The LOCK_STATUS
value is
GRANTED
for an acquired lock,
PENDING
for a lock that is being waited
for. You see PENDING
if one session holds
a write lock and another session is attempting to acquire a
lock having the same identifier.
The SQL interface to the locking service implements the loadable functions described in this section. For usage examples, see Using the Locking Service Function Interface.
The functions share these characteristics:
The return value is nonzero for success. Otherwise, an error occurs.
Namespace and lock names must be non-
NULL
, nonempty, and have a maximum length of 64 characters.Timeout values must be integers indicating how many seconds to wait to acquire locks before giving up with an error. If the timeout is 0, there is no waiting and the function produces an error if locks cannot be acquired immediately.
These locking service functions are available:
service_get_read_locks(
namespace
,lock_name
[,lock_name
] ...,timeout
)Acquires one or more read (shared) locks in the given namespace using the given lock names, timing out with an error if the locks are not acquired within the given timeout value.
service_get_write_locks(
namespace
,lock_name
[,lock_name
] ...,timeout
)使用给定的锁名称在给定的命名空间中获取一个或多个写(独占)锁,如果在给定的超时值内未获取锁,则超时并报错。
service_release_locks(
namespace
)service_get_read_locks()
对于给定的命名空间,使用和 释放在当前会话中获取的所有锁service_get_write_locks()
。命名空间中没有锁并不是错误。