DM online-ddl-scheme

概述

DDL 是数据库应用中必然会使用的一类 SQL。MySQL 虽然在 5.6 的版本以后支持了 online-ddl,但是也有或多或少的限制。比如 MDL 锁的获取,某些 DDL 还是需要以 Copy 的方式来进行,在生产业务使用中,DDL 执行过程中的锁表会一定程度上阻塞数据库的读取或者写入。

因此,gh-ost 以及 pt-osc 可以更优雅地把 DDL 在 MySQL 上面执行,把对读写的影响降到最低。

TiDB 根据 Google F1 的在线异步 schema 变更算法实现,在 DDL 过程中并不会阻塞读写。因此 gh-ost 和 pt-osc 在 online-schema-change 过程中产生的大量中间表的数据以及 binlog event,在 MySQL 与 TiDB 的数据同步过程中并不需要。

DM 作为 MySQL 同步到 TiDB 的工具,online-ddl-scheme 功能就是对上述两个 online-schema-change 的工具进行特殊的处理,以便更快完成所需的 DDL 同步。

如果想从源码方面了解 DM online-ddl-scheme,可以参考 DM 源码阅读系列文章(八)Online Schema Change 同步支持

配置

online-ddl-scheme 在 task 配置文件里面与 name 同级,例子详见下面配置 Example (完整的配置及意义可以参考 DM 完整配置文件示例

  1. # ----------- 全局配置 -----------
  2. ## ********* 基本信息配置 *********
  3. name: test # 任务名称,需要全局唯一
  4. task-mode: all # 任务模式,可设为 "full"、"incremental"、"all"
  5. is-sharding: true # 是否为分库分表合并任务
  6. meta-schema: "dm_meta" # 下游储存 `meta` 信息的数据库
  7. remove-meta: false # 是否在任务同步开始前移除该任务名对应的 `meta`(`checkpoint` 和 `onlineddl` 等)。
  8. enable-heartbeat: false # 是否开启 `heartbeat` 功能
  9. online-ddl-scheme: "gh-ost" # 目前仅支持 gh-ost 、pt
  10. target-database: # 下游数据库实例配置
  11. host: "192.168.0.1"
  12. port: 4000
  13. user: "root"
  14. password: "" # 如果不为空则需经过 dmctl 加密

online-schema-change: gh-ost

gh-ost 在实现 online-schema-change 的过程会产生 3 种 table:

  • gho 用于应用 DDL,待数据同步追上 origin table 之后会通过 rename 的方式替换 origin table。
  • ghc 用于存放 online-schema-change 相关的信息。
  • del 表是由 origin table rename 过来的。

DM 在同步过程中会把上述 table 分成 3 类:

  • ghostTable : _*_gho
  • trashTable : _*_ghc 、_*_del
  • realTable : 执行的 online-ddl 的 origin table

gh-ost 涉及的主要 SQL 如下:

  1. -- 1.
  2. Create /* gh-ost */ table `test`.`_test4_ghc` (
  3. id bigint auto_increment,
  4. last_update timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  5. hint varchar(64) charset ascii not null,
  6. value varchar(4096) charset ascii not null,
  7. primary key(id),
  8. unique key hint_uidx(hint)
  9. ) auto_increment=256 ;
  10. -- 2.
  11. Create /* gh-ost */ table `test`.`_test4_gho` like `test`.`test4` ;
  12. -- 3.
  13. Alter /* gh-ost */ table `test`.`_test4_gho` add column cl1 varchar(20) not null ;
  14. -- 4.
  15. Insert /* gh-ost */ into `test`.`_test4_ghc`;
  16. -- 5.
  17. Insert /* gh-ost `test`.`test4` */ ignore into `test`.`_test4_gho` (`id`, `date`, `account_id`, `conversion_price`, `ocpc_matched_conversions`, `ad_cost`, `cl2`)
  18. (select `id`, `date`, `account_id`, `conversion_price`, `ocpc_matched_conversions`, `ad_cost`, `cl2` from `test`.`test4` force index (`PRIMARY`)
  19. where (((`id` > _binary'1') or ((`id` = _binary'1'))) and ((`id` < _binary'2') or ((`id` = _binary'2')))) lock in share mode
  20. ) ;
  21. -- 6.
  22. Rename /* gh-ost */ table `test`.`test4` to `test`.`_test4_del`, `test`.`_test4_gho` to `test`.`test4`;

注意:

具体 gh-ost 的 SQL 会根据工具执行时所带的参数而变化。本文只列出主要的 SQL,具体可以参考 gh-ost 官方文档。

DM 对于 online-ddl-scheme: gh-ost 的处理

  • 不执行 _test4_ghc 的创建操作。
  • 不执行 _test4_gho 的创建操作,根据 ghost_schema、ghost_table 以及 dm_worker 的 server_id 删除下游 dm_meta.{task_name}_onlineddl 的记录,清理内存中的相关信息。

    1. DELETE FROM dm_meta.{task_name}_onlineddl WHERE id = {server_id} and ghost_schema = {ghost_schema} and ghost_table = {ghost_table};
  • 不执行 _test4_gho 的 DDL 操作。把执行的 DDL 记录到 dm_meta.{task_name}_onlineddl 以及内存中。

    1. REPLACE INTO dm_meta.{task_name}_onlineddl (id, ghost_schema , ghost_table , ddls) VALUES (......);
  • 只要不是 realtable 的 DML 全部不执行。

  • rename 拆分成两个 SQL。

    1. rename test.test4 to test._test4_del;
    2. rename test._test4_gho to test.test4;
  • 不执行 rename to _test4_del。当要执行 rename ghost_table to origin table 的时候,并不执行 rename,而是把步骤 3 内存中的 DDL 读取出来,然后把 ghost_table、ghost_schema 替换为 origin_table 以及对应的 schema 再执行。

    1. alter table test._test4_gho add column cl1 varchar(20) not null;
    2. --替换为
    3. alter table test.test4 add column cl1 varchar(20) not null;

online-schema-change: pt

pt-osc 在实现 online-schema-change 的过程会产生 2 种 table:

  • new 用于应用 DDL,待数据同步追上 origin table 之后会通过 rename 的方式替换 origin table。
  • old 是由 origin table rename 过来的。
  • 3 种 trigger:ptosc\*_ins、ptosc\*_upd、ptosc\*_del,用于在 pt_osc 过程中,同步 origin table 新产生的数据到 new。

DM 在同步过程中会把上述 table 分成 3 类:

  • ghostTable : _*_new
  • trashTable : _*_old
  • realTable : 执行的 online-ddl 的 origin table

pt-osc 主要涉及的 SQL如下:

  1. -- 1.
  2. CREATE TABLE `test`.`_test4_new` ( id int(11) NOT NULL AUTO_INCREMENT,
  3. date date DEFAULT NULL, account_id bigint(20) DEFAULT NULL, conversion_price decimal(20,3) DEFAULT NULL, ocpc_matched_conversions bigint(20) DEFAULT NULL, ad_cost decimal(20,3) DEFAULT NULL,cl2 varchar(20) COLLATE utf8mb4_bin NOT NULL,cl1 varchar(20) COLLATE utf8mb4_bin NOT NULL,PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ;
  4. -- 2.
  5. ALTER TABLE `test`.`_test4_new` add column c3 int
  6. -- 3.
  7. CREATE TRIGGER `pt_osc_test_test4_del` AFTER DELETE ON `test`.`test4` ...... ;
  8. CREATE TRIGGER `pt_osc_test_test4_upd` AFTER UPDATE ON `test`.`test4` ...... ;
  9. CREATE TRIGGER `pt_osc_test_test4_ins` AFTER INSERT ON `test`.`test4` ...... ;
  10. -- 4.
  11. INSERT LOW_PRIORITY IGNORE INTO `test`.`_test4_new` (`id`, `date`, `account_id`, `conversion_price`, `ocpc_matched_conversions`, `ad_cost`, `cl2`, `cl1`) SELECT `id`, `date`, `account_id`, `conversion_price`, `ocpc_matched_conversions`, `ad_cost`, `cl2`, `cl1` FROM `test`.`test4` LOCK IN SHARE MODE /*pt-online-schema-change 3227 copy table*/
  12. -- 5.
  13. RENAME TABLE `test`.`test4` TO `test`.`_test4_old`, `test`.`_test4_new` TO `test`.`test4`
  14. -- 6.
  15. DROP TABLE IF EXISTS `test`.`_test4_old`;
  16. DROP TRIGGER IF EXISTS `pt_osc_test_test4_del` AFTER DELETE ON `test`.`test4` ...... ;
  17. DROP TRIGGER IF EXISTS `pt_osc_test_test4_upd` AFTER UPDATE ON `test`.`test4` ...... ;
  18. DROP TRIGGER IF EXISTS `pt_osc_test_test4_ins` AFTER INSERT ON `test`.`test4` ...... ;

注意:

具体 pt-osc 的 SQL 会根据工具执行时所带的参数而变化。本文只列出主要的 SQL ,具体可以参考 pt-osc 官方文档。

DM 对于 online-ddl-scheme: pt 的处理

  1. 不执行 _test4_new 的创建操作。根据 ghost_schema 、 ghost_table 以及 dm_worker 的 server_id 删除下游 dm_meta.{task_name}_onlineddl 的记录,清理内存中的相关信息。

    1. DELETE FROM dm_meta.{task_name}_onlineddl WHERE id = {server_id} and ghost_schema = {ghost_schema} and ghost_table = {ghost_table};
  2. 不执行 _test4_new 的 DDL 操作。把执行的 DDL 记录到 dm_meta.{task_name}_onlineddl 以及内存中。

    1. REPLACE INTO dm_meta.{task_name}_onlineddl (id, ghost_schema , ghost_table , ddls) VALUES (......);
  3. 不执行 TiDB 不支持的相关 Trigger 操作。

  4. 只要不是 realtable 的 DML 全部不执行。
  5. rename 拆分成两个 SQL。

    1. rename test.test4 to test._test4_old;
    2. rename test._test4_new to test.test4;
  6. 不执行 rename to _test4_old。当要执行 rename ghost_table to origin table 的时候,并不执行 rename,而是把步骤 2 内存中的 DDL 读取出来,然后把 ghost_table、ghost_schema 替换为 origin_table 以及对应的 schema 再执行。

    1. ALTER TABLE `test`.`_test4_new` add column c3 int;
    2. --替换为
    3. ALTER TABLE `test`.`test4` add column c3 int;
  7. 不执行 _test4_old 以及 Trigger 的删除操作。