### 8.11.4 元数据锁定

[](<>)[](<>)[](<>)

MySQL 使用元数据锁定来管理对数据库对象的并发访问并确保数据一致性。元数据锁定不仅适用于表，还适用于模式、存储程序（过程、函数、触发器、计划事件）、表空间、使用[`GET_LOCK()`](locking-functions.html#function_get-lock)功能（见[第 12.15 节，“锁定功能”](locking-functions.html))，以及使用中描述的锁定服务获取的锁定[第 5.6.9.1 节，“锁定服务”](locking-service.html).

性能模式[`元数据锁`](performance-schema-metadata-locks-table.html)table 公开了元数据锁信息，这对于查看哪些会话持有锁、被阻塞等待锁等等很有用。有关详细信息，请参阅[第 27.12.13.3 节，“元数据\_锁表”](performance-schema-metadata-locks-table.html).

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

元数据锁不是表定义缓存的替代品，它的互斥锁和锁不同于`LOCK_open`互斥体。以下讨论提供了有关元数据锁定如何工作的一些信息。

-   [元数据锁获取](metadata-locking.html#metadata-lock-acquisition)

-   [元数据锁释放](metadata-locking.html#metadata-lock-release)

#### 元数据锁获取

如果给定锁有多个等待者，则首先满足最高优先级的锁请求，但与[`max_write_lock_count`](server-system-variables.html#sysvar_max_write_lock_count)系统变量。写锁请求的优先级高于读锁请求。然而，如果[`max_write_lock_count`](server-system-variables.html#sysvar_max_write_lock_count)设置为某个较低的值（例如 10），如果读锁定请求已经被传递，有利于 10 个写锁定请求，则读锁定请求可能优于挂起的写锁定请求。通常不会发生这种行为，因为[`max_write_lock_count`](server-system-variables.html#sysvar_max_write_lock_count)默认情况下具有非常大的值。

语句一个一个地获取元数据锁，而不是同时获取，并在过程中进行死锁检测。

DML 语句通常按照语句中提到表的顺序获取锁。

DDL 语句，[`锁定表`](lock-tables.html), 和其他类似的语句试图通过按名称顺序获取显式命名表上的锁来减少并发 DDL 语句之间可能出现的死锁数量。对于隐式使用的表（例如也必须锁定的外键关系中的表），可能会以不同的顺序获取锁。

例如，[`重命名表`](rename-table.html)是一个按名称顺序获取锁的 DDL 语句：

-   这[`重命名表`](rename-table.html)语句重命名`待定`到别的东西，并重命名`tblc`到`待定`：

    ```
    RENAME TABLE tbla TO tbld, tblc TO tbla;
    ```

    该语句按顺序获取元数据锁`待定`,`tblc`， 和`tbld`（因为`tbld`跟随`tblc`按名称顺序）：

-   这个略有不同的语句也重命名`待定`到别的东西，并重命名`tblc`到`待定`：

    ```
    RENAME TABLE tbla TO tblb, tblc TO tbla;
    ```

    在这种情况下，语句按顺序获取元数据锁`待定`, `tblb`， 和`tblc`（因为`tblb`先于`tblc`按名称顺序）：

    两个语句都获得了锁定`待定`和`tblc`, 按此顺序，但在剩余表名上的锁是在之前还是之后获取的不同`tblc`.

    当多个事务同时执行时，元数据锁的获取顺序会影响操作结果，如下例所示。

    从两张桌子开始`x`和`x_new`具有相同结构的。三个客户发出涉及这些表的语句：

    客户 1：


```
LOCK TABLE x WRITE, x_new WRITE;
```

该语句按名称顺序请求并获取写锁`x`和`x_new`.

客户 2：

```
INSERT INTO x VALUES(1);
```

语句请求并阻塞等待写锁定`x`.

客户 3：

```
RENAME TABLE x TO x_old, x_new TO x;
```

该语句以名称顺序请求排他锁`x`,`x_new`， 和`x_old`, 但阻塞等待锁定`x`.

客户 1：

```
UNLOCK TABLES;
```

该语句释放写锁`x`和`x_new`.排他锁请求`x`客户端 3 的写锁请求比客户端 2 的写锁请求具有更高的优先级，因此客户端 3 在`x`, 然后也开`x_new`和`x_old`，执行重命名，并释放其锁。客户端 2 然后获取它的锁定`x`，执行插入，并释放其锁定。

锁定获取订单导致[`重命名表`](rename-table.html)在执行之前[`插入`](insert.html).这`x`插入发生的表是被命名的表`x_new`当客户端 2 发出插入并重命名为`x`由客户 3：

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

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

现在从名为的表开始`x`和`新的_x`具有相同结构的。同样，三个客户端发出涉及这些表的语句：

客户 1：

```
LOCK TABLE x WRITE, new_x WRITE;
```

该语句按名称顺序请求并获取写锁`新的_x`和`x`.

客户 2：

```
INSERT INTO x VALUES(1);
```

语句请求并阻塞等待写锁定`x`.

客户 3：

```
RENAME TABLE x TO old_x, new_x TO x;
```

该语句以名称顺序请求排他锁`新的_x`,`旧_x`， 和`x`, 但阻塞等待锁定`新的_x`.

客户 1：

```
UNLOCK TABLES;
```

该语句释放写锁`x`和`新的_x`.为了`x`，唯一挂起的请求来自客户端 2，因此客户端 2 获取它的锁，执行插入，然后释放锁。为了`新的_x`，唯一挂起的请求是客户端 3，它被允许获取该锁（以及`旧_x`）。重命名操作仍然阻塞锁定`x`直到客户端 2 插入完成并释放其锁定。然后客户端 3 获取锁定`x`，执行重命名，并释放其锁定。

在这种情况下，锁获取顺序会导致[`插入`](insert.html)在执行之前[`重命名表`](rename-table.html).这`x`插入的地方是原始的`x`, 现在重命名为`旧_x`通过重命名操作：

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

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

如果并发语句中的锁获取顺序对应用程序的操作结果有影响，如前面的示例，您可以调整表名以影响锁获取的顺序。

[](<>)

元数据锁会根据需要扩展到由外键约束相关的表，以防止在相关表上同时执行冲突的 DML 和 DDL 操作。更新父表时，会在更新外键元数据时对子表进行元数据锁定。外键元数据归子表所有。

#### 元数据锁释放

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

这个原则不仅适用于事务表，也适用于非事务表。假设一个会话开始一个使用事务表的事务`吨`和非事务表`nt`如下：

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

服务器在两者上都持有元数据锁`吨`和`nt`直到交易结束。如果另一个会话尝试对任一表执行 DDL 或写入锁定操作，它会阻塞直到在事务结束时释放元数据锁定。例如，如果第二个会话尝试以下任何操作，则它会阻塞：

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

同样的行为适用于[`锁定表...阅读`](lock-tables.html).也就是说，显式或隐式启动的事务更新任何表（事务性或非事务性）阻塞并被`锁定表...阅读`为那张桌子。

如果服务器获取语法上有效但在执行期间失败的语句的元数据锁，它不会提前释放锁。锁释放仍然延迟到事务结束，因为失败的语句被写入二进制日志并且锁保护了日志的一致性。

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

期间获取的元数据锁[`准备`](prepare.html)语句一旦准备好就会发布，即使准备发生在多语句事务中。

从 MySQL 8.0.13 开始，对于 XA 事务`准备好的`状态，元数据锁在客户端断开连接和服务器重新启动之间保持不变，直到[`XA 提交`](xa.html)要么[`XA 回滚`](xa.html)被执行。
