{
    "componentChunkName": "component---src-templates-blog-blog-detail-tsx",
    "path": "/blog/introduction-to-the-dm-ddl-optimistic-coordination-model",
    "result": {"pageContext":{"blog":{"id":"Blogs_330","title":"DM 分库分表 DDL “乐观协调”模式介绍丨 TiDB 工具","tags":["数据迁移","DM"],"category":{"name":"产品技术解读"},"summary":"本文主要介绍 TiDB DM 分库分表 DDL 协调的“乐观协调”模式。乐观协调模式适用于上游灰度更新、发布的场景，或者是对上游数据库表结构变更过程中同步延迟比较敏感的场景。","body":"## 前言\n\nDM 支持在线执行分库分表的 DDL 语句（通称 Sharding DDL），[先前的文章](https://pingcap.com/zh/blog/introduction-to-the-dm-ddl-pessimistic-coordination-model)中，我们介绍了悲观模式，即当上游一个分表执行某一 DDL 后，这个分表的迁移会暂停，等待其他所有分表都执行了同样的 DDL 才在下游执行该 DDL 并继续数据迁移。\n\n悲观协调模式的优点是可以保证迁移到下游的数据不会出错，并且能兼容大部分的 DDL 语句，缺点是会暂停数据迁移而不利于对上游进行灰度变更、并显著地增加增量数据复制的延迟。有些客户可能会花数个月在单一分片执行 DDL，满意后才会更改其他分片的结构。在悲观同步的设定下，用来测试的分片的 DML 事件会大量积压，在恢复同步后无法正常运作。与此同时，悲观模式还要求所有分片必须以相同的顺序执行 DDL，否则会导致任务报错暂停。\n\n为此，DM 提供新的乐观协调模式，在一个分表上执行的 DDL，自动修改成兼容其他分表的 DDL 语句后立即应用到下游，不会阻挡任何分表执行的 DML 的迁移。**乐观协调模式适用于上游灰度更新、发布的场景，或者是对上游数据库表结构变更过程中同步延迟比较敏感的场景。**\n\n![1.png](https://img1.www.pingcap.com/prod/1_8e4e429342.png)\n<center>悲观协调和乐观协调的对比</center>\n\n## 原理\nDM worker 的所有 DML 会直接同步到下游（出错时例外）。\n\nDM worker 内嵌了一个小型 TiDB（通称 schema tracker），用来记录各个上游分表的表结构，当接收到来自上游的 DDL 后，会根据 schema tracker 里 DDL 的执行结果，把更新后的表结构转送给 DM master。DM master 将收到的不同分片的表结构合并成可兼容所有分片的 DML 的合成结构，即不同分片表结构的并集（此过程类似于 SQL 语句中的 JOIN 语句），然后根据合成的表结构和 DM worker 发来的表结构的不同处得到对应的 DDL 语句（即合成的表结构与原表结构的差集），同步到下游。\n\n![2.png](https://img1.www.pingcap.com/prod/2_fba427a4e8.png)\n\n具体的设计可以参考 [DM: Manage DDLs on Sharded Tables by Maximizing Schema Compatibility](https://github.com/pingcap/tiflow/blob/master/dm/docs/RFCS/20191209_optimistic_ddl.md).\n\n## 规则\n乐观 DDL 表结构合并的规则简单来说就是对列属性定义了一个**偏序关系**，对不同表的同一列进行排序，选择该偏序关系中的极大元。对于不可比较的列，则返回错误\n\n- null < not null\n- no default < default(x)\n- varchar(x) < varchar(y), where x< y\n- utf8 < utf8mb4\n- char < varchar\n- tinyint < smallint < mediumint < bigint\n- …\n\n对于被不存在或者被删除的列，我们把它定为最小的列\n\n如初始时表结构是相同的。\n\n![3.png](https://img1.www.pingcap.com/prod/3_625ecc7432.png)\n\ntbl2 添加第三列。前两列相同；tbl1 的第三列为空，所以保留 tbl2 的第三列。\n\n![4.png](https://img1.www.pingcap.com/prod/4_0aceaf7579.png)\n\ntbl2 删除第一列。第二列相同；tbl2 的第一列为空，所以保留 tbl1 的第一列。tbl1 的第三列为空，所以保留 tbl2 的第三列\n\n![5.png](https://img1.www.pingcap.com/prod/5_c9c6e80b29.png)\n\ntbl1 将第二列改为 varchar(10)，由于 varchar(5) < varchar(10)，所以保留 tbl1 的第二列\n\n![6.png](https://img1.www.pingcap.com/prod/6_3dfca1c2a0.png)\n\ntbl1 重命名第二列。现在 tbl1 和 tbl2 的第二列名字不一样，无法比较，DM 无法确定最终的表结构，所以任务会报错\n\n![7.png](https://img1.www.pingcap.com/prod/7_2fc62ed4e4.png)\n\n## 例子\n三个分片合并同步到 TiDB\n\n![8.png](https://img1.www.pingcap.com/prod/8_9fe638fc8a.png)\n\n① 在上游增加一列 Level。\nalter table `tbl00` add column `Level` int unsigned not null;\n\n![9.png](https://img1.www.pingcap.com/prod/9_27dc102f42.png)\n\ntbl00, tbl01, tbl02 的并集 tblMerge 是 {ID,NAME,Level}\ntblMerge 和 tbl 的差集是 {Level}，所以 DDL 是 add column Level\n\n此时下游 TiDB 要准备接受来自 tbl00 有 Level 的 DML、以及来自 tbl01 和 tbl02 没有 Level 的 DML，所以同步到下游时，自动改写成指定默认值的形式。\nalter table `tbl` add column `Level` int unsigned not null default 0;\n\n![10.png](https://img1.www.pingcap.com/prod/10_50031038ff.png)\n\n这时候各种 DML 毋需修改都可以同步到下游。\nupdate `tbl00` set `Level` = 9 where `ID` = 1;\ninsert into `tbl02` (`ID`, `Name`) values (27, 'Tony');\n\n![11.png](https://img1.www.pingcap.com/prod/11_94b6616a1c.png)\n\n② 在 tbl01 同样增加一列 Level。\nalter table `tbl01` add column `Level` int unsigned not null;\n\n![12.png](https://img1.www.pingcap.com/prod/12_9227f39155.png)\n\ntbl00, tbl01, tbl02 的并集 tblMerge 是 {ID,NAME,Level}\ntblMerge 和 tbl 的差集是 {}，所以 DDL 为空\n\n此时下游已经有相同的 Level 列了，所以 DM master 比较之后不做任何动作。\n\n③ 在 tbl01 刪除一列 Name。\nalter table `tbl01` drop column `Name`;\n\n![13.png](https://img1.www.pingcap.com/prod/13_3dbf7c870f.png)\n\ntbl00, tbl01, tbl02 的并集 tblMerge 是 {ID,NAME,Level}\ntblMerge 和 tbl 的差集是 {Level}，所以 DDL 为空\n\n此时下游仍需要接收来自 tbl00 和 tbl02 含 Name 的 DMLs，故不立删之，而是为这列也补上一个默认值。\nalter table `tbl` alter column `Name` set default “”;\n\n同样，各种 DML 仍可直接同步到下游。\ninsert into `tbl01` (`ID`, `Level`) values (15, 7);\nupdate `tbl00` set `Level` = 5 where `ID` = 5;\n\n![14.png](https://img1.www.pingcap.com/prod/14_fde7e36490.png)\n\n④ 在 tbl02 增加一列 Level。\ntbl00, tbl01, tbl02 的并集 tblMerge 是 {ID,NAME,Level}\ntblMerge 和 tbl 的差集是 {Level}，所以 DDL 为空\nalter table `tbl02` add column `Level` int unsigned not null;\n\n![15.png](https://img1.www.pingcap.com/prod/15_2dc2edfb97.png)\n\n此时所有分片都已有 Level 列，所以可以把作为兼容的默认值去掉。\nalter table `tbl` alter column `Level` drop default;\n\n⑤⑥ 在 tbl00 和 tbl02 各刪除一列 Name。\nalter table `tbl00` drop column `Name`;\nalter table `tbl02` drop column `Name`;\n\n![16.png](https://img1.www.pingcap.com/prod/16_fb6df0df51.png)\n\ntbl00, tbl01, tbl02 的并集 tblMerge 是 {ID,Level}\ntblMerge 和 tbl 的差集是 -{Name}，此差集是有符号的，所以 DDL 是 drop column Name\n\n到此步 Name 列也从所有分片消失了，所以可以安全从下游移除。\nalter table `tbl` drop column `Name`;\n\n![17.png](https://img1.www.pingcap.com/prod/17_fe14c8d864.png)\n\n## 限制\n**使用“乐观协调”模式有一定的风险，需要严格遵照以下方针：**\n\n* 执行每个批次的 DDL 前和后，要确保每个分表的结构达成一致。\n* 进行灰度 DDL 时，最好只集中在一个分表上测试。\n* 灰度完成后，在其他分表上尽量以最简单直接的 DDL 迁移到最终的 schema，而不要重新执行灰度测试中对或错的每一步。\n * 例如：在分表执行过 ADD COLUMN A INT; DROP COLUMN A; ADD COLUMN A FLOAT;，在其他分表直接执行 ADD COLUMN A FLOAT 即可，不需要三条 DDL 都执行一遍。\n* 执行 DDL 时要注意观察 DM 迁移状态。当迁移报错时，需要判断这个批次的 DDL 是否会造成数据不一致。\n\n更详细的介绍可参考[官网文档](https://docs.pingcap.com/zh/tidb-data-migration/stable/feature-shard-merge-optimistic)\n","date":"2021-12-22","author":"郭铭浩","fillInMethod":"writeDirectly","customUrl":"introduction-to-the-dm-ddl-optimistic-coordination-model","file":null,"relatedBlogs":[]}}},
    "staticQueryHashes": ["1327623483","1820662718","3081853212","3430003955","3649515864","4265596160","63159454"]}