1. 程式人生 > >pt-osc原理、限制、及與原生online-ddl比較

pt-osc原理、限制、及與原生online-ddl比較

1. pt-osc工作過程

  1. 建立一個和要執行 alter 操作的表一樣的新的空表結構(是alter之前的結構)
  2. 在新表執行alter table 語句(速度應該很快)
  3. 在原表中建立觸發器3個觸發器分別對應insert,update,delete操作
  4. 以一定塊大小從原表拷貝資料到臨時表,拷貝過程中通過原表上的觸發器在原表進行的寫操作都會更新到新建的臨時表
  5. Rename 原表到old表中,在把臨時表Rename為原表
  6. 如果有參考該表的外來鍵,根據alter-foreign-keys-method引數的值,檢測外來鍵相關的表,做相應設定的處理
  7. 預設最後將舊原表刪除

2. 常用選項說明

只介紹部分常用的選項

  • --host=xxx --user=xxx --password=xxx
    連線例項資訊,縮寫-h xxx -u xxx -p xxx,密碼可以使用引數--ask-pass 手動輸入。
  • --alter
    結構變更語句,不需要 ALTER TABLE關鍵字。與原始ddl一樣可以指定多個更改,用逗號分隔。
    • 絕大部分情況下表上需要有主鍵或唯一索引,因為工具在運行當中為了保證新表也是最新的,需要舊錶上建立 DELETE和UPDATE 觸發器,同步到新表的時候有主鍵會更快。個別情況是,當alter操作就是在c1列上建立主鍵時,DELETE觸發器將基於c1列。
    • 子句不支援 rename 去給表重新命名。
    • alter命令原表就不支援給索引重新命名,需要先drop再add,在pt-osc也一樣。(mysql 5.7 支援 RENAME INDEX old_index_name TO new_index_name)
      但給欄位重新命名,千萬不要drop-add,整列資料會丟失,使用change col1 col1_new type constraint(保持型別和約束一致,否則相當於修改 column type,不能online)
    • 子句如果是add column並且定義了not null,那麼必須指定default值,否則會失敗。
    • 如果要刪除外來鍵(名 fk_foo),使用工具的時候外來鍵名要加下劃線,比如--alter "DROP FOREIGN KEY _fk_foo"
    • D=db_name,t=table_name
      指定要ddl的資料庫名和表名

    • --max-load
      預設為Threads_running=25。每個chunk拷貝完後,會檢查 SHOW GLOBAL STATUS 的內容,檢查指標是否超過了指定的閾值。如果超過,則先暫停。這裡可以用逗號分隔,指定多個條件,每個條件格式: status指標=MAX_VALUE或者status指標:MAX_VALUE。如果不指定MAX_VALUE,那麼工具會這隻其為當前值的120%。
      因為拷貝行有可能會給部分行上鎖,Threads_running 是判斷當前資料庫負載的絕佳指標。

    • --max-lag
      預設1s。每個chunk拷貝完成後,會檢視所有複製Slave的延遲情況(Seconds_Behind_Master)。要是延遲大於該值,則暫停複製資料,直到所有從的滯後小於這個值。--check-interval配合使用,指定出現從庫滯後超過 max-lag,則該工具將睡眠多長時間,預設1s,再檢查。如--max-lag=5 --check-interval=2
      熟悉percona-toolkit的人都知道--recursion-method可以用來指定從庫dsn記錄。另外,如果從庫被停止,將會永遠等待,直到從開始同步,並且延遲小於該值。

    • --chunk-time
      預設0.5s,即拷貝資料行的時候,為了儘量保證0.5s內拷完一個chunk,動態調整chunk-size的大小,以適應伺服器效能的變化。
      也可以通過另外一個選項--chunk-size禁止動態調整,即每次固定拷貝 1k 行,如果指定則預設1000行,且比 chunk-time 優先生效

    • --set-vars
      使用pt-osc進行ddl要開一個session去操作,set-vars可以在執行alter之前設定這些變數,比如預設會設定--set-vars "wait_timeout=10000,innodb_lock_wait_timeout=1,lock_wait_timeout=60"
      因為使用pt-osc之後ddl的速度會變慢,所以預計2.5h只能還不能改完,記得加大wait_timeout

    • --dry-run
      建立和修改新表,但不會建立觸發器、複製資料、和替換原表。並不真正執行,可以看到生成的執行語句,瞭解其執行步驟與細節,和--print配合最佳。。

    • --execute
      確定修改表,則指定該引數。真正執行alter。–dry-run與–execute必須指定一個,二者相互排斥

3. 使用疑惑(限制)

3.1 原表上不能有觸發器存在

這個很容易理解,pt-osc會在原表上建立3個觸發器,而一個表上不能同時有2個相同型別的觸發器,為簡單通用起見,只能一棍子打死。
所以如果要讓它支援有觸發器存在的表也是可以實現的,思路就是:先找到原表觸發器定義;重寫原表觸發器;最後階段將原表觸發器定義應用到新表。

3.2 通過觸發器寫資料到臨時新表,會不會出現資料不一致或異常

這其實是我的一個顧慮,因為如果update t1,觸發update t2,但這條資料還沒copy到t2,不就有異常了嗎?後臺通過開啟general_log,看到它建立的觸發器:

1
2
3
4
5
6
7
8
9
10
11
12
    6165 Query     CREATE TRIGGER `pt_osc_confluence_sbtest3_del` AFTER DELETE ON `confluence`.`sbtest3` 
        FOR EACH ROW DELETE IGNORE FROM `confluence`.`_sbtest3_new` WHERE `confluence`.`_sbtest3_new`.`id` <=> OLD.`id`
    6165 Query     CREATE TRIGGER `pt_osc_confluence_sbtest3_upd` AFTER UPDATE ON `confluence`.`sbtest3` 
        FOR EACH ROW REPLACE INTO `confluence`.`_sbtest3_new` (`id`, `k`, `c`, `pad`) VALUES (NEW.`id`, NEW.`k`, NEW.`c`, NEW.`pad`)
    6165 Query     CREATE TRIGGER `pt_osc_confluence_sbtest3_ins` AFTER INSERT ON `confluence`.`sbtest3` 
        FOR EACH ROW REPLACE INTO `confluence`.`_sbtest3_new` (`id`, `k`, `c`, `pad`) VALUES (NEW.`id`, NEW.`k`, NEW.`c`, NEW.`pad`)

並且copy操作是:

    6165 Query     INSERT LOW_PRIORITY IGNORE INTO `confluence`.`_sbtest3_new` (`id`, `k`, `c`, `pad`) 
         SELECT `id`, `k`, `c`, `pad` FROM `confluence`.`sbtest3` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '4692805')) AND ((`id` <= '4718680')) 
        LOCK IN SHARE MODE /*pt-online-schema-change 46459 copy nibble*/

 

在原表上update,新臨時表上是replace into整行資料,所以達到有則更新,無則插入。同時配合後面的 insert ignore,保證這條資料不會因為重複而失敗。

3.3 為什麼外來鍵那麼特殊

假設 t1 是要修改的表,t2 有外來鍵依賴於 t1_t1_new 是 alter t1 產生的新臨時表。
這裡的外來鍵不是看t1上是否存在外來鍵,而是作為子表的 t2。主要問題在 rename t1 時,t1“不存在”導致t2的外來鍵認為參考失敗,不允許rename。
pt-osc提供--alter-foreign-keys-method選項來決定怎麼處理這種情況:

  • rebuild_constraints,優先採用這種方式
    • 它先通過 alter table t2 drop fk1,add _fk1 重建外來鍵參考,指向新表
    • 再 rename t1 t1_old, _t1_new t1 ,交換表名,不影響客戶端
    • 刪除舊錶 t1_old
      但如果字表t2太大,以致alter操作可能耗時過長,有可能會強制選擇 drop_swap。
      涉及的主要方法在 pt-online-schema-change 檔案的 determine_alter_fk_methodrebuild_constraintsswap_tables三個函式中。
  • drop_swap
    • 禁用t2表外來鍵約束檢查 FOREIGN_KEY_CHECKS=0
    • 然後 drop t1 原表
    • 再 rename _t1_new t1
      這種方式速度更快,也不會阻塞請求。但有風險,第一,drop表的瞬間到rename過程,原表t1是不存在的,遇到請求會報錯;第二,如果因為bug或某種原因,舊錶已刪,新表rename失敗,那就太晚了,但這種情況很少見。
      我們的開發規範決定,即使表間存在外來鍵參考關係,也不通過表定義強制約束。

3.4 在使用之前需要對磁碟容量進行評估

使用OSC會使增加一倍的空間,包括索引
而且在 Row Based Replication 下,還會寫一份binlog。不要想當然使用--set-vars去設定 sql_log_bin=0,因為在這個session級別,alter語句也要在從庫上執行,除非你對從庫另有打算。

4. 使用 pt-osc原生 5.6 online ddl相比,如何選擇

  • online ddl在必須copy table時成本較高,不宜採用
  • pt-osc工具在存在觸發器時,不適用
  • 修改索引、外來鍵、列名時,優先採用online ddl,並指定 ALGORITHM=INPLACE
  • 其它情況使用pt-osc,雖然存在copy data
  • pt-osc比online ddl要慢一倍左右,因為它是根據負載調整的
  • 無論哪種方式都選擇的業務低峰期執行
  • 特殊情況需要利用主從特性,先alter從庫,主備切換,再改原主庫

5.使用pt-osc修改主鍵時注意

 

使用 pt-online-schema-change 做線上ddl最新增普通索引、列,修改列型別、新增預設值等使用比較常規,但涉及到要修改的是主鍵時就有點棘手。在我修改線上例項過程中,有這樣的需求,不妨先思考一下怎麼做才好:

1
原表上有個複合主鍵,現在要新增一個自增id作為主鍵,如何進行

 

會涉及到以下修改動作:

  1. 刪除複合主鍵定義
  2. 新增新的自增主鍵
  3. 原複合主鍵欄位,修改成唯一索引

如果你夠聰明,應該會把這三個操作放在同一個 alter table 命令執行。percona手冊裡有兩個地方對修改主鍵進行了特殊註解:

–alter
A notable exception is when a PRIMARY KEY or UNIQUE INDEX is being created from existing columns as part of the ALTER clause; in that case it will use these column(s) for the DELETE trigger.

–[no]check-alter

  • DROP PRIMARY KEY
    If –alter contain DROP PRIMARY KEY (case- and space-insensitive), a warning is printed and the tool exits unless –dry-run is specified. Altering the primary key can be dangerous, but the tool can handle it. The tool’s triggers, particularly the DELETE trigger, are most affected by altering the primary key because the tool prefers to use the primary key for its triggers. You should first run the tool with –dry-run and –print and verify that the triggers are correct.

由上一篇文章 pt-online-schema-change使用說明、限制與比較 可知,pt-osc會在原表t1上建立 AFTER DELETE/UPDATE/INSERT 三個觸發器,修改主鍵影響最大的就是 DELETE 觸發器:新表t2上的主鍵欄位在舊錶t1上不存在,無法根據主鍵條件觸發刪除新表t2資料。but the tool can handle it,原因是pt-osc把觸發器改成了下面的形式:

1
2
3
4
CREATE TRIGGER `pt_osc_confluence_sbtest3_del` AFTER DELETE ON `confluence`.`sbtest3` FOR EACH ROW DELETE IGNORE FROM `confluence`.`_sbtest3_new` 
WHERE `confluence`.`_sbtest3_new`.`id` <=> OLD.`id` AND `confluence`.`_sbtest3_new`.`k` <=> OLD.`k`

注:sbtest3表上以(id,k)作為複合主鍵

 

但是如果id或k列上沒有索引,這個刪除的代價非常高,所以一定要同時新增複合(唯一)索引 (id,k) .

而對於INSERT,UPDATE的觸發器,依然是 REPLACE INTO語法,因為它採用的是先插入,如果違反主鍵或唯一約束,則根據主鍵或意義約束刪除這條資料,再執行插入。(但是注意你不能依賴於新表的主鍵遞增,因為如果原表有update,新表就會先插入這一條,導致id與原表記錄所在順序不一樣)

所以如果使用pt-osc去修改刪除主鍵,務必同時新增原主鍵為 UNIQUE KEY,否則很有可能導致效能問題:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ pt-online-schema-change --user=ecuser --password=ecuser --host=10.0.201.34  \
--alter "DROP PRIMARY KEY,add column pk int auto_increment primary key,add unique key uk_id_k(id,k)" \
D=confluence,t=sbtest3 --print --dry-run

--alter contains 'DROP PRIMARY KEY'.  Dropping and altering the primary key can be dangerous, 
especially if the original table does not have other unique indexes.  ==>注意 dry-run的輸出

ALTER TABLE `confluence`.`_sbtest3_new` DROP PRIMARY KEY,add column pk int auto_increment primary key,add unique key uk_id_k(id,k)
Altered `confluence`.`_sbtest3_new` OK.
Using original table index PRIMARY for the DELETE trigger instead of new table index PRIMARY because ==> 使用原表主鍵值判斷
the new table index uses column pk which does not exist in the original table.

CREATE TRIGGER `pt_osc_confluence_sbtest3_del` AFTER DELETE ON `confluence`.`sbtest3` FOR EACH ROW DELETE IGNORE FROM `confluence`.`_sbtest3_new` 
WHERE `confluence`.`_sbtest3_new`.`id` <=> OLD.`id` AND `confluence`.`_sbtest3_new`.`k` <=> OLD.`k`