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

PingCAP發表於2021-12-30

前言

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

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

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


<center>悲觀協調和樂觀協調的對比<center>

原理

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 table tbl00 add column Level int 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 table tbl add column Level int unsigned not null default 0;

這時候各種 DML 毋需修改都可以同步到下游。
update tbl00 set Level = 9 where ID = 1;
insert into tbl02 (ID, Name) values (27, 'Tony');

② 在 tbl01 同樣增加一列 Level。
alter table tbl01 add 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 table tbl alter column Name set default “”;

同樣,各種 DML 仍可直接同步到下游。
insert into tbl01 (ID, Level) values (15, 7);
update tbl00 set Level = 5 where ID = 5;

④ 在 tbl02 增加一列 Level。
tbl00, tbl01, tbl02 的並集 tblMerge 是 {ID,NAME,Level}
tblMerge 和 tbl 的差集是 {Level},所以 DDL 為空
alter table tbl02 add column Level int unsigned not null;

此時所有分片都已有 Level 列,所以可以把作為相容的預設值去掉。
alter table tbl alter column Level drop default;

⑤⑥ 在 tbl00 和 tbl02 各刪除一列 Name。
alter table tbl00 drop column Name;
alter table tbl02 drop column Name;

tbl00, tbl01, tbl02 的並集 tblMerge 是 {ID,Level}
tblMerge 和 tbl 的差集是 -{Name},此差集是有符號的,所以 DDL 是 drop column Name

到此步 Name 列也從所有分片消失了,所以可以安全從下游移除。
alter table tbl drop 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 是否會造成資料不一致。

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

相關文章