MySQL ONLINE DDL 工具之gh-ost
gh-ost也是一種線上的解決DDL的方案,不依賴於觸發器,它是通過模擬從庫,在row binlog中獲取增量變更,再非同步應用到ghost表中。目前gh-ost已經收穫了將近一萬的star,並且在持續更新中。
2.1 主要工作流程
gh-ost工作流程如下:
-
建立影子表
和在影子表上執行變更
這兩步和pt-osc基本相同,只是建立的影子表名稱不一樣,這裡是_t1_gho; -
建立 binlog streamer
:這一步的作用是為了同步t1表上的增量DML到 _t1_gho上。gh-ost會偽裝成一個從庫節點,讀取資料庫(可能是叢集中的主節點或者從節點)的binlog,然後解析binlog,獲取到對於t1的相應操作,然後轉化成對 _t1_gho 表的操作; -
同步資料
:這一步也是迴圈同步資料,每次迴圈也會監控資料庫的負載等,只是在確定需要同步資料範圍的上下邊界和ppt-osc有所不同;-
首先確定全部需要同步資料範圍的上限邊界;
select /* gh-ost `test`.`t1` */ `id` from `test`.`t1` order by `id` asc limit 1 // 確定全部需要同步資料範圍的下邊界MIN(id) select /* gh-ost `test`.`t1` */ `id` from `test`.`t1` order by `id` desc limit 1 // 確定全部需要同步資料範圍的上邊界MAX(id)
-
確認本次迴圈同步資料範圍的上邊界
select /* gh-ost `test`.`t1` */ `id` from `test`.`t1` where ((`id` > _binary'8991')) and ((`id` < _binary'10000') or ((`id` = _binary'10000'))) order by `id` asc limit 1 offset 998 // 其中 8991是上次迴圈的上邊界值 // 10000 是需要同步資料範圍的上邊界MAX(id) // offset 998 這個值和--chunk-size的設定有關,--chunk-size值減 1
-
同步資料
insert /* gh-ost `test`.`t1` */ ignore into `test`.`_t1_gho` (`id`, `c1`, `c2`, `c3`) (select `id`, `c1`, `c2`, `c3` from `test`.`t1` force index (`PRIMARY`) where (((`id` > _binary'8991')) and ((`id` < _binary'9990') or ((`id` = _binary'9990')))) lock in share mode // 其中 8991 是上次迴圈的上邊界值 // 其中 9990 是步驟b中獲取到的資料
-
-
增量應用binlog
這裡說明一下,同步資料和增量應用binlog是同時進行的,沒有確定的時間先後順序,只要在binlog裡發現有相應變更就會在影子表上重放。
-
同步資料和應用增量binlog
-
若迴圈執行3-b時,若獲取到資料則正常進入步驟3-c;
-
若迴圈執行3-b時未獲取到資料,則說明剩餘未同步的資料小於--chunk-size的值,執行以下SQL確認本次的上邊界值
select /* gh-ost `test`.`t1` */ `id` from ( select `id` from `test`.`t1` where ((`id` > _binary'9990')) and ((`id` < _binary'10000') or ((`id` = _binary'10000'))) order by `id` asc limit 999 ) select_osc_chunk order by `id` desc limit 1
若上面SQL獲取到值,則迴圈執行步驟3-c同步資料;若未獲取到值則資料已經完全同步。
-
-
更改表名
:在更改表名之前會先對錶加寫鎖,這點需要注意。原始表和影子表 cut-over 切換是原子性切換,但是基本都是通過兩個會話的操作來完成。大致流程如下:
- 會話 Cn1(這裡代表一個或者多個會話): 對t1表正常執行DML操作。
- 會話 gh1 : 建立_t1_del 防止提前RENAME表,導致資料丟失。
- 會話 gh1 : 執行LOCK TABLES t1 WRITE,_t1_del WRITE。
- 會話 Cn2 : LOCK TABLES之後進來的會話操作會被阻塞。
- 會話 gh2 : 設定鎖等待時間並執行RENAME
set session lock_wait_timeout:=1 rename /* gh-ost */ table `test`.`t1` to `test`.`_t1_del`, `test`.`_t1_gho` to `test`.`t1`; // gh2 的操作因為 gh1 鎖表而等待,後續有其他新發起的對錶t1上的操作也會阻塞
-
會話 gh1 會通過SQL 檢查是否已經有會話在執行RENAME操作並且在等待MDL鎖,此時會檢測到gh2。
-
會話gh1 : 基於上面執行的結果,執行DROP TABLE _t1_del。
-
會話gh1 : 執行UNLOCK TABLES; 此時gh2的rename命令第一個被執行。而其他會話如Cn2的請求之後開始執行。
這裡涉及到的原理是基於 MySQL 內部機制:被 LOCK TABLE 阻塞之後,執行 RENAME 的優先順序高於 DML,也即先執行 RENAME TABLE ,然後執行 DML,即使DML發起的時間早於RENAME的時間。
另外要先在表上加寫鎖的根本原因其實還是為了保證資料的一致性。gh-ost在同步原表上的變更操作是使用的拼接binlog的形式,和原表上發生的操作不屬於同一個事務。所以在收尾 cut-over 時要先對錶加上寫鎖,阻塞原表上的變更,待增量的binlog應用完成後再去更改表名,這樣才能保證資料不丟失。而pt-osc不需要顯式加鎖是因為原表和影子表上的更新是在同一個事務裡的,原表變更完成,影子表上的表更也就完成了,在做RENAME時會阻塞原表上的更新,RENAME完成後變更就發生在了新表上了(之前的影子表)。
-
停止binlog streamer
,處理收尾工作,結束。
2.2 使用限制和風險
1)使用要求和限制
-
必須有一個從庫的binlog是row模式,並且binlog_row_image 設定成full。主節點沒有特殊要求;
-
主備節點上,目標表的結構必須是相同的;
-
不支援外來鍵約束和觸發器
-
目標表上必須有主鍵或者唯一鍵,gh-ost使用該鍵遍歷表
-
主鍵或者唯一鍵不能包含為空的列。也就是說鍵中的列的屬性應為 NOT NULL,或鍵中的列是可以為空 但是實際資料中沒有NULL 值。
-
預設情況下,若是唯一鍵中包含可為空的列,gh-ost不會執行,使用者可以使用 --allow-nullable-unique-key ,但是依然要確保實際資料沒有NULL值,若是有NULL 值,gh-ost不能保證能將其完全遷移走。
-
-
不允許遷移存在相同名稱且大小寫不同的表;
-
不支援多源複製,不過可以嘗試使用 --allow-on-master 選項連線到主庫
-
雙主複製,只支援一臺例項上有寫請求的情況
-
不支援表更名的操作 :ALTER TABLE ... RENAME TO some_other_name
-
PXC叢集不能使用該工具。gh-ost在更改表名階段是使用不同的執行緒執行LOCK TABLE,RENME ,DROP TABLE 操作的,由於PXC的驗證機制這會導致執行操作的PXC節點發生死鎖。
2)使用風險
-
更改列名
-
使用change方式更改非主鍵或者非唯一鍵的列名
更改列名gh-ost也會發出警告,並退出:
FATAL gh-ost believes the ALTER statement renames columns, as follows: map[id:id_new]; as precaution, you are asked to confirm gh-ost is correct, and provide with `--approve-renamed-columns`, and we're all happy. Or you can skip renamed columns via `--skip-renamed-columns`, in which case column data may be lost
修改列名也會有丟失資料的風險,所以需要自己先確認gh-ost的行為是否符合預期,同時提供了兩個選項來告訴gh-ost如何處理重新命名的列。
--approve-renamed-columns:該選項是告訴gh-ost要同步重新命名列的資料
--skip-renamed-columns:跳過重新命名的列,也就是對於重新命名的列上的資料不進行同步
-
更改主鍵或者唯一鍵的列名
若表上只有主鍵或者唯一鍵,gh-ost會直接退出,並丟擲以下日誌:
FATAL No shared unique key can be found after ALTER! Bailing out
若表上既有主鍵又有唯一鍵,更改其中一個的列名,gh-ost會選擇另一個鍵做為 共享唯一鍵。
-
先刪除列然後新增改名後列
該方式和 pt-online-schema-change一樣會導致資料丟失。
-
-
對於外來鍵的處理
預設gh-ost不支援有外來鍵引用或者包含外來鍵的表變更。但是提供了相應的選項;
--skip-foreign-key-checks :跳過外來鍵的檢查,這時不會對外來鍵進行檢查,最終會導致表上外來鍵丟失或者外來鍵引用的表不存在;
-discard-foreign-keys :該選項明確告訴gh-ost不在影子表上建立外來鍵,最終變更後的表會丟失外來鍵。
-
建立唯一鍵或者主鍵
使用gh-ost建立唯一鍵或者主鍵不會有相關警告,由於使用insert ignore into的方式同步資料,所以有可能會造成資料丟失。
-
鎖爭用問題
在更改表名前會對錶加寫鎖,若表上操作頻繁可能會導致鎖等待。
-
在開啟半同步複製情況下,若設定 rpl_semi_sync_master_wait_point = AFTER_SYNC,在獲取同步資料的上下邊界時,有可能獲取不到的最新上下邊界,導致資料丟失。github上已經有人提交相關bug gh-ost may lose data when performing DDL on mysql with semi-sync replication enabled
2.3 豐富的監控功能
gh-ost在執行期間也會監控資料庫的負載,和pt-osc類似,只是引數不同。另外gh-ost也提供了一些互動功能,動態的修改需要監控的指標和閾值等,這裡不再贅述。