DM 分庫分表 DDL “樂觀協調” 模式介紹丨TiDB 工具分享

前言

DM 支持在線執行分庫分表的 DDL 語句(通稱 Sharding DDL),先前的文章中,我們介紹了悲觀模式,即當上遊一個分表執行某一 DDL 後,這個分表的遷移會暫停,等待其他所有分表都執行了同樣的 DDL 纔在下游執行該 DDL 並繼續數據遷移。

 

悲觀協調模式的優點是可以保證遷移到下游的數據不會出錯,並且能兼容大部分的 DDL 語句,缺點是會暫停數據遷移而不利於對上游進行灰度變更、並顯著地增加增量數據複製的延遲。有些客戶可能會花數個月在單一分片執行 DDL,滿意後纔會更改其他分片的結構。在悲觀同步的設定下,用來測試的分片的 DML 事件會大量積壓,在恢復同步後無法正常運作。與此同時,悲觀模式還要求所有分片必須以相同的順序執行 DDL,否則會導致任務報錯暫停。

 

爲此,DM 提供新的樂觀協調模式,在一個分表上執行的 DDL,自動修改成兼容其他分表的 DDL 語句後立即應用到下游,不會阻擋任何分表執行的 DML 的遷移。樂觀協調模式適用於上游灰度更新、發佈的場景,或者是對上游數據庫表結構變更過程中同步延遲比較敏感的場景。

悲觀協調和樂觀協調的對比

 

原理

DM worker 的所有 DML 會直接同步到下游(出錯時例外)。

 

DM worker 內嵌了一個小型 TiDB(通稱 schema tracker),用來記錄各個上游分表的表結構,當接收到來自上游的 DDL 後,會根據 schema tracker 裏 DDL 的執行結果,把更新後的表結構轉送給 DM master。DM master 將收到的不同分片的表結構合併成可兼容所有分片的 DML 的合成結構,即不同分片表結構的並集(此過程類似於 SQL 語句中的 JOIN 語句),然後根據合成的表結構和 DM worker 發來的表結構的不同處得到對應的 DDL 語句(即合成的表結構與原表結構的差集),同步到下游。

(具體的設計可以參考 DM:Manage DDLs on Sharded Tables by Maximizing Schema Compatibility.)

 

規則

樂觀 DDL 表結構合併的規則簡單來說就是對列屬性定義了一個偏序關係,對不同表的同一列進行排序,選擇該偏序關係中的極大元。對於不可比較的列,則返回錯誤:

 

  • null < not null

  • no default < default(x)

  • varchar(x) < varchar(y), where x< y

  • utf8 < utf8mb4

  • char < varchar

  • tinyint < smallint < mediumint < bigint

  •  

對於被不存在或者被刪除的列,我們把它定爲最小的列

 

如初始時表結構是相同的。

tbl2 添加第三列。前兩列相同;tbl1 的第三列爲空,所以保留 tbl2 的第三列。

tbl2 刪除第一列。第二列相同;tbl2 的第一列爲空,所以保留 tbl1 的第一列。tbl1 的第三列爲空,所以保留 tbl2 的第三列

tbl1 將第二列改爲 varchar(10),由於 varchar(5) < varchar(10),所以保留 tbl1 的第二列

tbl1 重命名第二列。現在 tbl1 和 tbl2 的第二列名字不一樣,無法比較,DM 無法確定最終的表結構,所以任務會報錯

 

例子

三個分片合併同步到 TiDB:

 

① 在上游增加一列 Level。

alter tabletbl00add columnLevelint unsigned not null;

tbl00, tbl01, tbl02 的並集 tblMerge 是 {ID,NAME,Level}

tblMerge 和 tbl 的差集是 {Level},所以 DDL 是 add column Level

 

此時下游 TiDB 要準備接受來自 tbl00 有 Level 的 DML、以及來自 tbl01 和 tbl02 沒有 Level 的 DML,所以同步到下游時,自動改寫成指定默認值的形式。

alter tabletbladd column Level int unsigned not null default 0;

 

這時候各種 DML 毋需修改都可以同步到下游。

updatetbl00set Level = 9 whereID= 1;

insert intotbl02(ID,Name) values (27, 'Tony');

 

② 在 tbl01 同樣增加一列 Level。

alter tabletbl01add column Level int unsigned not null;

tbl00, tbl01, tbl02 的並集 tblMerge 是 {ID,NAME,Level}

tblMerge 和 tbl 的差集是 {},所以 DDL 爲空

 

此時下游已經有相同的 Level 列了,所以 DM master 比較之後不做任何動作。

 

③ 在 tbl01 刪除一列 Name。

alter table tbl01 drop column Name;

tbl00, tbl01, tbl02 的並集 tblMerge 是 {ID,NAME,Level}

tblMerge 和 tbl 的差集是 {Level},所以 DDL 爲空

 

此時下游仍需要接收來自 tbl00 和 tbl02 含 Name 的 DMLs,故不立刪之,而是爲這列也補上一個默認值。

alter tabletblalter columnNameset default “”;

 

同樣,各種 DML 仍可直接同步到下游。

insert into tbl01(ID,Level) values (15, 7);
update tbl00set 
Level= 5 where ID = 5;

 

④ 在 tbl02 增加一列 Level。

tbl00, tbl01, tbl02 的並集 tblMerge 是 {ID,NAME,Level}

tblMerge 和 tbl 的差集是 {Level},所以 DDL 爲空

alter table  tbl02 add columnLevelint unsigned not null;

此時所有分片都已有 Level 列,所以可以把作爲兼容的默認值去掉。

alter table tbl alter column  Level drop default;

 

⑤⑥ 在 tbl00 和 tbl02 各刪除一列 Name。

alter table tbl00 drop columnName;

alter tabletbl02drop columnName;

tbl00, tbl01, tbl02 的並集 tblMerge 是 {ID,Level}

tblMerge 和 tbl 的差集是 -{Name},此差集是有符號的,所以 DDL 是 drop column Name

 

到此步 Name 列也從所有分片消失了,所以可以安全從下游移除。

alter table tbdrop column Name;

 

限制

使用 “樂觀協調” 模式有一定的風險,需要嚴格遵照以下方針:

 

  • 執行每個批次的 DDL 前和後,要確保每個分表的結構達成一致。

  • 進行灰度 DDL 時,最好只集中在一個分表上測試。

  • 灰度完成後,在其他分表上儘量以最簡單直接的 DDL 遷移到最終的 schema,而不要重新執行灰度測試中對或錯的每一步。

  • 例如:在分表執行過 ADD COLUMN A INT; DROP COLUMN A; ADD COLUMN A FLOAT;,在其他分表直接執行 ADD COLUMN A FLOAT 即可,不需要三條 DDL 都執行一遍。

  • 執行 DDL 時要注意觀察 DM 遷移狀態。當遷移報錯時,需要判斷這個批次的 DDL 是否會造成數據不一致。

 

更詳細的介紹可參考官網文檔

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章