diff --git a/dm/manually-handling-sharding-ddl-locks.md b/dm/manually-handling-sharding-ddl-locks.md index eb8e3bd26b9e..9c701f290063 100644 --- a/dm/manually-handling-sharding-ddl-locks.md +++ b/dm/manually-handling-sharding-ddl-locks.md @@ -5,11 +5,15 @@ aliases: ['/docs-cn/tidb-data-migration/dev/manually-handling-sharding-ddl-locks # 手动处理 Sharding DDL Lock -DM (Data Migration) 使用 sharding DDL lock 来确保分库分表的 DDL 操作可以正确执行。绝大多数情况下,该锁定机制可自动完成;但在部分异常情况发生时,需要使用 `shard-ddl-lock` 手动处理异常的 DDL lock。 +在分库分表合并迁移发生异常时,你需要使用 `shard-ddl-lock` 命令手动处理 sharding DDL lock。 + +在 DM 中有[悲观模式](/dm/feature-shard-merge-pessimistic.md)与[乐观模式](/dm/feature-shard-merge-optimistic.md)两种分库分表合并迁移模式。两种模式中的 `shard-ddl-lock` 的概念存在差异: + +- 在悲观模式中,DM 使用 sharding DDL lock 来确保分库分表的 DDL 操作可以正确执行。绝大多数情况下,该锁定机制可自动完成;但在部分异常情况发生时,需要使用 `shard-ddl-lock` 手动处理异常的 DDL lock。 +- 在乐观模式中,DM 使用表结构与 DDL 信息来确保分库分表的 DDL 操作可以正确执行。sharding DDL Lock 在乐观模式中用于表示是否所有分表可以生成兼容表结构。绝大多数情况不需人为干预,仅当分库分表表结构存在不可解冲突时,才需要使用 `shard-ddl-lock` 手动处理异常表结构。 > **注意:** > -> - 本文档只适用于悲观协调模式下 sharding DDL lock 的处理。 > - 本文档的命令在交互模式中进行,因此在以下命令示例中未添加转义字符。在命令行模式中,你需要添加转义字符,防止报错。 > - 不要轻易使用 `shard-ddl-lock unlock` 命令,除非完全明确当前场景下使用这些命令可能会造成的影响,并能接受这些影响。 > - 在手动处理异常的 DDL lock 前,请确保已经了解 DM 的[分库分表合并迁移原理](/dm/feature-shard-merge-pessimistic.md#实现原理)。 @@ -59,7 +63,7 @@ Use "dmctl shard-ddl-lock [command] --help" for more information about a command 使用 `shard-ddl-lock [task] [flags]` 命令,查询当前 DM-master 上存在的 DDL lock 信息。 -例如: +悲观模式: ```bash shard-ddl-lock test @@ -77,8 +81,8 @@ shard-ddl-lock test "ID": "test-`shard_db`.`shard_table`", # lock 的 ID 标识,当前由任务名与 DDL 对应的 schema/table 信息组成 "task": "test", # lock 所属的任务名 "mode": "pessimistic" # shard DDL 协调模式,可为悲观模式 "pessimistic" 或乐观模式 "optimistic" - "owner": "mysql-replica-01", # lock 的 owner(在悲观模式时为第一个遇到该 DDL 的 source ID),在乐观模式时总为空 - "DDLs": [ # 在悲观模式时为 lock 对应的 DDL 列表,在乐观模式时总为空 + "owner": "mysql-replica-01", # lock 的 owner,在悲观模式时为第一个遇到该 DDL 的 source ID + "DDLs": [ # 在悲观模式时为 lock 对应的 DDL 列表 "USE `shard_db`; ALTER TABLE `shard_db`.`shard_table` DROP COLUMN `c2`;" ], "synced": [ # 已经收到对应 MySQL 实例内所有分表 DDL 的 source 列表 @@ -94,13 +98,58 @@ shard-ddl-lock test +乐观模式: + +```bash +shard-ddl-lock test +``` + +
+期望输出 + +``` +{ + "result": true, # 查询 lock 操作本身是否成功 + "msg": "", # 查询 lock 操作失败时的原因或其它描述信息(如不存在任务 lock) + "locks": [ # 当前存在的 lock 信息列表 + { + "ID": "test-`shardddl`.`tb`", # lock 的 ID 标识,当前由任务名与 DDL 对应的 schema/table 信息组成 + "task": "test", # lock 所属的任务名 + "mode": "optimistic", # shard DDL 协调模式,可为悲观模式 "pessimistic" 或乐观模式 "optimistic" + "owner": "mysql-replica-02-`shardddl1`.`tb1`", # lock 的 owner,在乐观模式时为 DDL 存在冲突的上游分表的 source-`database`.`table` 格式信息 + "DDLs": [ # 在乐观模式时为 lock 冲突时的 DDL 列表,未出现冲突时为空 + "ALTER TABLE `shardddl`.`tb` MODIFY COLUMN `b` INT DEFAULT -1" + ], + "synced": [ # 已经收到对应 MySQL 实例内所有分表 DDL 的上游 source-`database`.`table` 分表列表 + "mysql-replica-01-`shardddl1`.`tb1`" + ], + "unsynced": [ # 尚未收到对应 MySQL 实例内所有分表 DDL 的上游 source-`database`.`table` 分表列表 + "mysql-replica-02-`shardddl1`.`tb1`", + "mysql-replica-02-`shardddl1`.`tb2`" + ] + } + ] +} +``` + +
+ ### `shard-ddl-lock unlock` -用于主动请求 DM-master 解除指定的 DDL lock,包括的操作:请求 owner 执行 DDL 操作,请求其他非 owner 的 DM-worker 跳过 DDL 操作,移除 DM-master 上的 lock 信息。 +用于主动请求 DM-master 解除指定的 DDL lock。 > **注意:** > -> `shard-ddl-lock unlock` 当前仅对悲观协调模式 (`pessimistic`) 下产生的 lock 有效。 +> 在 DM v6.0 以前版本,`shard-ddl-lock unlock` 仅对悲观协调模式 (`pessimistic`) 下产生的 lock 有效。 + + +
+ +[悲观模式](/dm/feature-shard-merge-pessimistic.md) `unlock` 包括以下操作: + +* 请求 owner 执行 DDL 操作。 +* 请求其他非 owner 的 DM-worker 跳过 DDL 操作。 +* 移除 DM-master 上的 lock 信息。 {{< copyable "shell-regular" >}} @@ -126,7 +175,7 @@ Global Flags: -s, --source strings MySQL Source ID. ``` -`shard-ddl-lock unlock` 命令支持以下参数: +悲观模式相关参数: + `-o, --owner`: - flag 参数,string,可选 @@ -141,6 +190,66 @@ Global Flags: - 非 flag 参数,string,必选 - 指定需要执行 unlock 操作的 DDL lock ID(即 `shard-ddl-lock` 返回结果中的 `ID`) +
+ +
+ +[乐观模式](/dm/feature-shard-merge-optimistic.md) `unlock` 包括以下操作: + +* 请求指定的处于冲突状态的上游表执行或跳过冲突 DDL 操作。 +* 对其他非指定的表不会进行任何操作。 +* 若操作后 DM-master 可以为所有分表生成兼容表结构,则 DM-master 上的 lock 信息将被自动移除。 + +{{< copyable "shell-regular" >}} + +```bash +shard-ddl-lock unlock -h +``` + +``` +Unlock un-resolved DDL locks forcely + +Usage: + dmctl shard-ddl-lock unlock [flags] + +Flags: + -a, --action string accept skip/exec values which means whether to skip or execute ddls (default "skip") + -d, --database string database name of the table + -f, --force-remove force to remove DDL lock + -h, --help help for unlock + -o, --owner string source to replace the default owner + -t, --table string table name + +Global Flags: + -s, --source strings MySQL Source ID. +``` + +乐观模式相关参数: + ++ `-a, --action`: + - flag 参数,string,可选 + - 不指定时,请求指定的上游表跳过该冲突 DDL 操作;指定为 exec 时,请求该上游表执行引起冲突的 DDL 操作 + - 除非执行前已对下游合表表结构进行了适配修改,否则不应该指定为 exec 执行冲突 DDL。 + ++ `-s, --source`: + - flag 参数,string,乐观模式必填且目前只支持写一个 + - 指定需要执行 unlock 操作的上游的 source ID,可通过 `shard-ddl-lock` 命令获取 + ++ `-d, --database`: + - flag 参数,string,乐观模式必填且目前只支持写一个 + - 指定需要执行 unlock 操作的上游的数据库名称,可通过 `shard-ddl-lock` 命令获取 + ++ `-t, --table`: + - flag 参数,string,乐观模式必填且目前只支持写一个 + - 指定需要执行 unlock 操作的上游的表名称,可通过 `shard-ddl-lock` 命令获取 + ++ `lock-id`: + - 非 flag 参数,string,必选 + - 指定需要执行 unlock 操作的 DDL lock ID(即 `shard-ddl-lock` 返回结果中的 `ID`) + +
+
+ 以下是一个使用 `shard-ddl-lock unlock` 命令的示例: {{< copyable "shell-regular" >}} @@ -158,7 +267,7 @@ shard-ddl-lock unlock test-`shard_db`.`shard_table` ## 支持场景 -目前,使用 `shard-ddl-lock unlock` 命令仅支持处理以下两种 sharding DDL lock 异常情况。 +目前,使用 `shard-ddl-lock unlock` 命令仅支持处理以下三种 sharding DDL lock 异常情况。 ### 场景一:部分 MySQL source 被移除 @@ -401,3 +510,244 @@ MySQL 及 DM 操作与处理流程如下: #### 手动处理后的影响 手动 unlock sharding DDL lock 后,后续的 sharding DDL 将可以自动正常迁移。 + +### 场景三:乐观模式协调过程中出现表结构冲突 + +#### Lock 异常原因 + +在 DM-master 尝试自动协调乐观 DDL,需要等待所有 MySQL source 的表结构达到一致状态,锁信息才会被清除(详见[乐观分表合并协调原理](/dm/feature-shard-merge-optimistic.md#原理))。在 sharding DDL 迁移过程中,如果分表的 DDL 生成了不一致的表结构,例如部分分表添加 default 0 的列、而另一部分分表添加 default 1 的列,将造成 DM-master 无法生成兼容表结构,从而使得锁无法自动 unlock。 + +#### 手动处理示例 + +假设上游有 MySQL-1 (`mysql-replica-01`) 和 MySQL-2 (`mysql-replica-02`) 两个实例,其中 MySQL-1 中有 `shardddl1`.`tb1` 一个表,MySQL-2 中有 `shardddl1`.`tb1` 和 `shardddl1`.`tb2` 两个表。现在需要将这三个表合并后迁移到下游 TiDB 的 `shardddl`.`tb` 表中。 + +初始表结构如下: + +{{< copyable "sql" >}} + +```sql +SHOW CREATE TABLE shardddl1.tb1; +``` + +``` ++---------------+-------------------------------------------+ +| Table | Create Table | ++---------------+-------------------------------------------+ +| tb1 | CREATE TABLE `tb1` ( + `a` int(11) NOT NULL, + `b` int(11) NOT NULL, + PRIMARY KEY (`a`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin | ++---------------+-------------------------------------------+ +``` + +上游 MySQL-1 实例下的分表将执行以下 DDL 语句变更表结构: + +{{< copyable "sql" >}} + +```sql +ALTER TABLE shardddl*.tb* MODIFY b INT DEFAULT 0; +``` + +上游 MySQL-2 实例下的分表将执行以下 DDL 语句变更表结构: + +{{< copyable "sql" >}} + +```sql +ALTER TABLE shardddl*.tb* MODIFY b INT DEFAULT -1; +``` + +MySQL 及 DM 操作与处理流程如下: + +1. `mysql-replica-01` 对应的分表执行了对应的 DDL 操作进行表结构变更。 + + {{< copyable "sql" >}} + + ```sql + ALTER TABLE shardddl1.tb1 MODIFY b INT DEFAULT 0; + ``` + +2. DM-worker 接收到 `mysql-replica-01` 分表的 DDL 之后,将对应的 DDL 信息发送给 DM-master,DM-master 创建相应的 DDL lock。 +3. 使用 `shard-ddl-lock` 查看当前的 DDL lock 信息。 + + {{< copyable "shell-regular" >}} + + ```bash + shard-ddl-lock test + ``` + + ``` + { + "result": true, + "msg": "", + "locks": [ + { + "ID": "test-`shardddl`.`tb`", + "task": "test", + "mode": "optimistic", + "owner": "", + "DDLs": [ + ], + "synced": [ + "mysql-replica-01-`shardddl1`.`tb1`" + ], + "unsynced": [ + "mysql-replica-02-`shardddl1`.`tb1`", + "mysql-replica-02-`shardddl1`.`tb2`" + ] + } + ] + } + ``` + +4. 由于业务需要,`mysql-replica-02` 对应的分表添加的列默认值为 -1,但下游最终结构希望默认值为 0。 +5. DM-worker 接受到 `mysql-replica-02` 分表的 DDL 之后,将对应的 DDL 信息发送给 DM-master,DM-master 无法生成兼容表结构,MySQL-2 上的同步暂停。此时使用 `shard-ddl-lock` 和 `query-status test` 命令可以查到具体问题: + + {{< copyable "shell-regular" >}} + + ```bash + shard-ddl-lock test + ``` + +
+ 期望输出 + + ``` + { + "result": true, + "msg": "", + "locks": [ + { + "ID": "test-`shardddl`.`tb`", + "task": "test", + "mode": "optimistic", + "owner": "mysql-replica-02-`shardddl1`.`tb1`", + "DDLs": [ + "ALTER TABLE `shardddl`.`tb` MODIFY COLUMN `b` INT DEFAULT -1" + ], + "synced": [ + "mysql-replica-01-`shardddl1`.`tb1`" + ], + "unsynced": [ + "mysql-replica-02-`shardddl1`.`tb1`", + "mysql-replica-02-`shardddl1`.`tb2`" + ] + } + ] + } + ``` + +
+ + ```bash + query-status test + ``` + +
+ 期望输出 + + ``` + ... + { + "result": true, + "msg": "", + "sourceStatus": { + "source": "mysql-replica-02", + "worker": "worker2", + ... + }, + "subTaskStatus": [ + { + "name": "test", + "stage": "Running", + "unit": "Sync", + "result": null, + "unresolvedDDLLockID": "", + "sync": { + ... + "synced": false, + "binlogType": "local", + "secondsBehindMaster": "0", + "blockDDLOwner": "mysql-replica-02-`shardddl1`.`tb1`", + "conflictMsg": "[code=11111:class=functional:scope=internal:level=medium], Message: fail to try sync the optimistic shard ddl lock test-`shardddl`.`tb`: there will be conflicts if DDLs ALTER TABLE `shardddl`.`tb` MODIFY COLUMN `b` INT DEFAULT -1 are applied to the downstream. old table info: CREATE TABLE `tbl`(`a` INT(11) NOT NULL, `b` INT(11) NOT NULL, PRIMARY KEY (`a`)) CHARSET UTF8MB4 COLLATE UTF8MB4_BIN, new table info: CREATE TABLE `tbl`(`a` INT(11) NOT NULL, `b` INT(11) DEFAULT -1, PRIMARY KEY (`a`)) CHARSET UTF8MB4 COLLATE UTF8MB4_BIN, Workaround: Please use `show-ddl-locks` command for more details." + } + } + ] + } + ... + ``` + +
+ +6. DM-master 上 ID 为 ```test-`shardddl`.`tb` ``` 的 lock 无法为 `mysql-replica-02` 上的 `shardddl1`.`tb1` 的 DDL 生成兼容表结构,`shard-ddl-lock` 返回的 `unsynced` 中一直包含 `mysql-replica-02` 的信息。 + +7. 使用 `shard-ddl-lock unlock` 来请求 DM-master 主动 unlock 第一个分表 (`shardddl1`.`tb1`) 的 DDL lock。 + + 执行 `shard-ddl-lock unlock` 命令。根据第 5 步中 `query-status` 命令输出中的 `blockDDLOwner` 信息,指定 `-s`、`-d`、`-t` 这三个参数,每个参数有且仅有一个值。 + + {{< copyable "shell-regular" >}} + + ```bash + shard-ddl-lock unlock 'test-`shardddl`.`tb`' -s mysql-replica-02 -d shardddl1 -t tb1 --action skip + ``` + + ``` + { + "result": true, + "msg": "" + } + ``` + + 当存在任意 MySQL source 报错时,`result` 将为 `false`,此时请仔细检查各 MySQL source 的错误是否是预期可接受的。 + +8. 重复第 7 步,unlock 第二个分表 (`shardddl1`.`tb2`) 的 DDL lock。 +9. 使用 `shard-ddl-lock` 确认 DDL lock 是否被成功 unlock。 + + ```bash + shard-ddl-lock test + ``` + + ``` + { + "result": true, + "msg": "no DDL lock exists", + "locks": [ + ] + } + ``` + +10. 查看下游 TiDB 中的表结构是否变更成功。 + + {{< copyable "sql" >}} + + ```sql + SHOW CREATE TABLE shardddl.tb; + ``` + + ``` + +-------------+--------------------------------------------------+ + | Table | Create Table | + +-------------+--------------------------------------------------+ + | tb | CREATE TABLE `tb` ( + `a` int(11) NOT NULL, + `b` int(11) DEFAULT '0', + PRIMARY KEY (`a`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin + +-------------+--------------------------------------------------+ + ``` + +11. 使用 `query-status` 确认迁移任务是否正常。 + +#### 手动处理后的影响 + +使用 `shard-ddl-lock unlock` 手动执行 unlock 操作后,由于出错分表的表结构中仍然包含了不兼容的表信息,如果不进行处理,则当下次 sharding DDL 到达时,仍会出现 lock 无法自动完成迁移的情况。 + +因此,在手动解锁 DDL lock 后,需要再执行以下操作: + +1. 使用 `pause-task` 暂停运行中的任务。 +2. 使用 `binlog-schema update` 更新跳过的分表的表结构为兼容的表结构。 +3. 使用 `resume-task` 恢复任务运行。 + +> **注意:** +> +> 在某些场景下运行 `shard-ddl-lock unlock` 时,如果下游不进行手动处理,可能会引起上下游数据不一致。