8.11.4 元数据锁定

MySQL 使用元数据锁定来 Management 对数据库对象的并发访问并确保数据一致性。元数据锁定不仅适用于 table,而且还适用于架构,存储的程序(过程,函数,触发器,计划的事件),table 空间,通过GET_LOCK()函数获取的用户锁(请参见第 12.14 节“锁定功能”)以及通过第 28.3.1 节“锁定服务”中描述的锁定服务获取的锁。 。

Performance Schema metadata_lockstable 公开了元数据锁定信息,这对于查看哪些会话持有锁定,被阻止 await 锁定等很有用。有关详细信息,请参见第 25.12.12.1 节,“ The metadata_lockstable”

元数据锁定确实涉及一些开销,随着查询量的增加而增加。多个查询尝试访问同一对象的情况越多,元数据争用就会增加。

元数据锁定不能代替 table 定义高速缓存,并且其互斥锁和锁定与LOCK_open互斥锁不同。以下讨论提供了有关元数据锁定如何工作的一些信息。

元数据锁获取

如果给定锁有多个服务员,则首先满足最高优先级的锁请求,但与max_write_lock_count系统变量有关的异常除外。写锁定请求比读锁定请求具有更高的优先级。但是,如果max_write_lock_count设置为某个较低的值(例如 10),则如果已将读取锁定请求传递给了 10 个写入锁定请求,则读取锁定请求可能比挂起的写入锁定请求优先。通常,因为max_write_lock_count默认具有非常大的值,所以不会发生此现象。

语句不是一个接一个地获取元数据锁,并在此过程中执行死锁检测。

DML 语句通常以在语句中提到 table 的 Sequences 获取锁。

DDL 语句LOCK TABLES和其他类似的语句通过按名称 Sequences 获取对显式命名的 table 的锁,试图减少并发 DDL 语句之间可能出现的死锁。对于隐式使用的 table(例如,具有外键关系的 table 也必须被锁定),可以以不同的 Sequences 获取锁。

例如,RENAME TABLE是按名称 Sequences 获取锁的 DDL 语句:

  • RENAME TABLE语句将tbla重命名为其他名称,并将tblc重命名为tbla
RENAME TABLE tbla TO tbld, tblc TO tbla;

该语句按 Sequences 获取tblatblctbld上的元数据锁(因为tbld按照名称 Sequences 在tblc之后):

  • 这个稍有不同的语句还将tbla重命名为其他名称,并将tblc重命名为tbla
RENAME TABLE tbla TO tblb, tblc TO tbla;

在这种情况下,该语句按 Sequences 获取tblatblbtblc上的元数据锁(因为tblb在名称 Sequences 上先于tblc):

这两个语句都按此 Sequences 获取tblatblc上的锁,但是不同之处在于是否在tblc之前或之后获取了对剩余 table 名的锁定。

如以下示例所示,当多个事务同时执行时,元数据锁获取 Sequences 可能会在操作结果上产生差异。

从两个具有相同结构的 tablexx_new开始。三个 Client 发出涉及这些 table 的语句:

Client 1:

LOCK TABLE x WRITE, x_new WRITE;

该语句按名称 Sequences 在xx_new上请求并获取写锁定。

Client 2:

INSERT INTO x VALUES(1);

该语句请求并阻止 awaitx上的写锁定。

Client 3:

RENAME TABLE x TO x_old, x_new TO x;

该语句按名称 Sequences 请求xx_newx_old上的互斥锁,但阻止 awaitx上的锁。

Client 1:

UNLOCK TABLES;

该语句释放对xx_new的写锁定。Client 端 3 对x的排他锁定请求具有比 Client 端 2 的写锁定请求更高的优先级,因此 Client 端 3 在x上获得其锁定,然后在x_newx_old上获得其锁定,执行重命名并释放其锁定。然后,Client 端 2 获得对x的锁定,执行插入操作,然后释放其锁定。

锁定获取 Sequences 导致RENAME TABLEINSERT之前执行。发生插入的x是当 Client 端 2 发出插入时被命名为x_new的 table,并被 Client 端 3 重命名为x

mysql> SELECT * FROM x;
+------+
| i    |
+------+
|    1 |
+------+

mysql> SELECT * FROM x_old;
Empty set (0.01 sec)

现在从具有相同结构的名为xnew_x的 table 开始。同样,三个 Client 发出涉及这些 table 的语句:

Client 1:

LOCK TABLE x WRITE, new_x WRITE;

该语句按名称 Sequences 在new_xx上请求并获取写锁定。

Client 2:

INSERT INTO x VALUES(1);

该语句请求并阻止 awaitx上的写锁定。

Client 3:

RENAME TABLE x TO old_x, new_x TO x;

该语句按名称 Sequences 请求new_xold_xx上的互斥锁,但阻止 awaitnew_x上的锁。

Client 1:

UNLOCK TABLES;

该语句释放对xnew_x的写锁定。对于x,唯一未决的请求是 Client 端 2 发出的,因此 Client 端 2 获得其锁,执行插入操作,然后释放该锁。对于new_x,唯一待处理的请求是 Client 端 3 允许其获取该锁(以及old_x的锁)。重命名操作仍然会阻止x上的锁定,直到 Client 端 2 插入完成并释放其锁定为止。然后,Client 端 3 获取x上的锁,执行重命名,然后释放其锁。

在这种情况下,锁定获取 Sequences 导致INSERTRENAME TABLE之前执行。插入的x是原始x,现在已通过重命名操作重命名为old_x

mysql> SELECT * FROM x;
Empty set (0.01 sec)

mysql> SELECT * FROM old_x;
+------+
| i    |
+------+
|    1 |
+------+

如前例所示,如果并发语句中锁获取的 Sequences 对应用程序的操作结果有所不同,则可以调整 table 名以影响锁获取的 Sequences。

元数据锁定释放

为了确保事务可串行化,服务器不得允许一个会话对在另一会话中未完成的显式或隐式启动的事务中使用的 table 执行数据定义语言(DDL)语句。服务器通过获取在事务中使用的 table 上的元数据锁并将这些锁的释放推迟到事务结束之前来实现。table 上的元数据锁可防止更改 table 的结构。这种锁定方法的含义是,在一个事务内的事务正在使用的 table 不能在事务结束之前由其他会话在 DDL 语句中使用。

该原理不仅适用于事务 table,而且还适用于非事务 table。假设会话开始使用事务 tablet和非事务 tablent的事务,如下所示:

START TRANSACTION;
SELECT * FROM t;
SELECT * FROM nt;

服务器在tnt上都保留元数据锁,直到事务结束。如果另一个会话在任一 table 上尝试 DDL 或写锁定操作,它将阻塞,直到在事务结束释放元数据锁定为止。例如,如果第二个会话尝试执行以下任何操作,则它将阻塞:

DROP TABLE t;
ALTER TABLE t ...;
DROP TABLE nt;
ALTER TABLE nt ...;
LOCK TABLE t ... WRITE;

锁 table...阅读的行为相同。即,更新任何 table(事务性或非事务性)的显式或隐式启动的事务将对该 table 进行阻塞并被LOCK TABLES ... READ阻塞。

如果服务器获取语法上有效但在执行过程中失败的语句的元数据锁,则它不会尽早释放该锁。锁定释放仍被推迟到事务结束,因为失败的语句被写入二进制日志,并且锁定保护了日志的一致性。

在自动提交模式下,每个语句实际上是一个完整的事务,因此为该语句获取的元数据锁仅保留到该语句的末尾。

一旦准备好语句,即使在多语句事务中进行准备,也会释放在PREPARE语句期间获取的元数据锁。