## 模块 5：无阻塞在线 DDL 演进表结构

![demo_203-tidb-x-online-ddl.jpg 85%](https://lab-static.pingcap.com/images/2025/11/27/d7bde260caeff9b78d19ea3c1b9c4469ae41ecda.jpg)

在 TiDB Cloud 中，将系统变量 `tidb_enable_metadata_lock` 设置为 `OFF`，即可避免 DML 事务锁阻塞 DDL 操作，真正实现在线、无阻塞的表结构变更。

其核心优势在于：DML 与 DDL 语句可以并发作用于同一个表，互不阻塞，因为 DDL 操作是异步执行的。这让表结构在 TiDB 中的演进变得异常简便。一般来说，只要你的应用代码事先兼容新旧表结构，数据库管理员（DBA）就可随时执行 DDL 操作，变更表结构，无需预留业务停机窗口。

### 步骤 1. 建立两个数据库连接

- 点击 [终端2](ssh://monitor1-ssh#2) 打开一个新的终端。

- 在 [终端1](ssh://monitor1-ssh#1)，如果尚未连接到 TiDB，请执行以下命令：

  ```bash
  mysql \
   --comments -u '${LAB:SERVERLESS_CLUSTER_USERNAME}' -p'${LAB:SERVERLESS_CLUSTER_PASSWORD}' \
   -h ${LAB:SERVERLESS_CLUSTER_HOST} -P ${LAB:SERVERLESS_CLUSTER_PORT} \
   --ssl-mode=VERIFY_IDENTITY --ssl-ca=/etc/ssl/certs/ca-certificates.crt \
   -D ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}
  ```

- 在 [终端2](ssh://monitor1-ssh#2) 同样建立一条数据库连接：

  ```bash
  mysql \
   --comments -u '${LAB:SERVERLESS_CLUSTER_USERNAME}' -p'${LAB:SERVERLESS_CLUSTER_PASSWORD}' \
   -h ${LAB:SERVERLESS_CLUSTER_HOST} -P ${LAB:SERVERLESS_CLUSTER_PORT} \
   --ssl-mode=VERIFY_IDENTITY --ssl-ca=/etc/ssl/certs/ca-certificates.crt \
   -D ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}
  ```

### 步骤 2. 理解元数据锁的默认行为

- 在 [终端1](ssh://monitor1-ssh#1) 与 [终端2](ssh://monitor1-ssh#2) 中分别确认默认状态下元数据锁已开启：

  ```sql
  SHOW SESSION VARIABLES LIKE 'tidb_enable_metadata_lock';
  ```

  ```output
  +---------------------------+-------+
  | Variable_name             | Value |
  +---------------------------+-------+
  | tidb_enable_metadata_lock | ON    |
  +---------------------------+-------+
  1 row in set (0.01 sec)
  ```

- 下面以添加列为例，探索表结构变更过程中元数据锁的作用。

- 在 [终端1](ssh://monitor1-ssh#1) 执行以下事务控制和 DML 操作（更新一本书的库存）。注意，此时不仅有行级锁，表级元数据锁也已生效。因为事务尚未提交，锁会一直存在。

  ```sql
  BEGIN;
  UPDATE books SET stock = stock+1 WHERE id = 1;
  ```

  ```output
  Query OK, 1 row affected (0.01 sec)
  Rows matched: 1  Changed: 1  Warnings: 0
  ```

- 此时在 [终端2](ssh://monitor1-ssh#2) 对同一个表运行如下 DDL（添加字段），会发现该 DDL **被阻塞**，原因是另一个会话存在未提交的 DML 事务：

  ```sql
  ALTER TABLE books ADD COLUMN (valid_for_training INT);
  ```

- 回到 [终端1](ssh://monitor1-ssh#1)，通过提交事务释放元数据锁：

  ```sql
  COMMIT;
  ```

  ```output
  Query OK, 0 rows affected (0.02 sec)
  ```

- 再回到 [终端2](ssh://monitor1-ssh#2)，你会发现 DDL 操作只有在事务被提交、锁释放后才会完成——这可能会延迟一段时间（例如示例中等待 1 分 37.93 秒）。实际等待看你的切换和操作速度。

### 步骤 3. 启用无阻塞在线 DDL

- 在 [终端1](ssh://monitor1-ssh#1) 执行如下命令关闭元数据锁：

  ```sql
  SET GLOBAL tidb_enable_metadata_lock = OFF;
  ```

- 在 [终端1](ssh://monitor1-ssh#1) 和 [终端2](ssh://monitor1-ssh#2) 验证元数据锁已关闭：

  ```sql
  SHOW SESSION VARIABLES LIKE 'tidb_enable_metadata_lock';
  ```

  ```output
  +---------------------------+-------+
  | Variable_name             | Value |
  +---------------------------+-------+
  | tidb_enable_metadata_lock | OFF   |
  +---------------------------+-------+
  1 row in set (0.01 sec)
  ```

- 再次以添加列为例，此时 DML 不会阻塞 DDL。

- 在 [终端1](ssh://monitor1-ssh#1) 运行以下事务和 DML：

  ```sql
  BEGIN;
  UPDATE books SET stock = stock+2 WHERE id = 1;
  ```

  ```output
  Query OK, 1 row affected (0.01 sec)
  Rows matched: 1  Changed: 1  Warnings: 0
  ```

- 在 [终端2](ssh://monitor1-ssh#2) 向同一表运行如下 DDL（添加字段），你会发现 DDL 会立即成功且不会被其他会话中的 DML 操作阻塞。这就是无阻塞在线 DDL 的效果。

  ```sql
  ALTER TABLE books ADD COLUMN (isbn INT);
  ```

  ```output
  Query OK, 0 rows affected (0.65 sec)
  ```

- 回到 [终端1](ssh://monitor1-ssh#1) 尝试提交事务。由于事务创建之后、提交之前表结构发生了变化（加入了新列），所以提交会失败，事务被回滚。这同样源自于无阻塞在线 DDL 的机制。

  ```sql
  COMMIT;
  ```

  ```output
  ERROR 8028 (HY000): Information schema is changed during the execution of the statement(for example, table definition may be updated by other DDL ran in parallel). If you see this error often, try increasing `tidb_max_delta_schema_count`. [try again later]
  ```

- 此时你可能会想：“无阻塞 DDL 会让正在进行的 DML 报错回滚，为什么还要用？” 关键在于 DDL 一般属于计划内的变更/运维事件。例如，增加 `isbn` 列满足业务新需求时，你可以先更新应用代码兼容该字段，并做好接收 `ERROR 8028` 的容错准备，事务失败时可简单重试。好处是，数据库无需强制让你的业务（即 DML）等 DDL 跑完再继续。结合 TiDB 的无阻塞 DDL 和持续交付（CI/CD）实践，你可以真正实现全在线表结构变更，无业务宕机。

- 我们还为不同语言提供了练习，无阻塞 DDL 和 `ERROR 8028` 的具体处理可详见如下实验：[MySQL Connector/Python 与 TiDB Cloud 实践](/labs/demo_405)、[JDBC 与 TiDB Cloud 实践](/labs/demo_701)。

总结一下，是否开启 `tidb_enable_metadata_lock`，应结合业务需求与系统负载特点权衡：

  | `tidb_enable_metadata_lock` | 优点                                               | 缺点                                         |
  | :---------------------------| :--------------------------------------------------| :--------------------------------------------|
  | ON（默认）                  | DML 不受表结构变更影响                            | DML 会阻塞 DDL 操作                          |
  | OFF                         | DML 不会阻塞 DDL                                  | DML 需处理表结构变更（可能会错误回滚）        |

- 最后，恢复变量到默认设置：

  ```sql
  SET GLOBAL tidb_enable_metadata_lock = ON;
  ```

- 现在让我们进入下一个模块，继续学习。
