pt-online-schema-change 原理簡單介紹

pt-osc工作的提前是表有主鍵或者唯一鍵,如果沒有主鍵或者唯一鍵,爲何不行採用pt-osc來進行DDL操作,

這還得從它的工作原理說起。pt-osc的原理是新建一個更改後的表結構,再在原表上創建三個觸發器,分別

對應update,delete,insert操作,以保證新的DML操作能同步到新表,再按chunk拆分copy原表的數據到新表,

最後最一次rename操作,因此整個過程都是幾乎不堵塞的,rename 操作會堵塞,但是rename操作時間很短,

因此也可以認爲是不堵塞的。下面將其中主要流程分開說明:

三個觸發器,分別對應update,delete,insert。其中update和insert都對應replace into操作到新表,replace into

操作纔是保證原表和新表記錄一致性的關鍵,reaplce into操作是依賴主鍵或者唯一鍵來工作的,如果沒有主鍵或者

唯一鍵,那麼repalce into操作就是簡單的insert into操作,那麼當update原表一條記錄,觸發器則replace這條記錄

到新表,由於沒有主鍵或者唯一鍵,則直接insert into update之後的記錄到新表,然後在copy階段,再次讀到該條

記錄,採用insert ignore操作,但是沒有主鍵和唯一鍵,又是直接insert into 記錄到新表,這樣就導致新表多出來

一條記錄,若該記錄更新較多,則會在新表插入多條記錄,從而導致數據不一致。對於insert 一條記錄到原表,

也是先通過觸發器insert到新表一條記錄,然後在copy階段,則可能再次被insert 一次,導致數據不一致。

對於delete操作,不管是先copy還是先delete再copy,都不會導致數據不一致。

如果有主鍵或者唯一鍵,情況會如何?對於update原表一條記錄,replace into一條記錄到新表,由於有主鍵或者唯一鍵,

如果新表以及copy了這條數據,則通過replace into更新這條記錄和原表一致,如果還沒copy這套記錄,那麼則insert into

這條記錄到新表保持數據的一致,當copy階段,操作到這條記錄時,由於是採用的insert ignore,因此對於已經存在的

記錄,則直接忽略,數據還是一致的,如果沒有該記錄,則insert 該條記錄,保持一致。因此在有主鍵和唯一鍵的情況下,

update操作不會導致數據不一致。

對於insert 原表一條記錄,原表上的觸發器replace into到新表,由於之前新表沒有這條記錄,因此replace into直接將這條

記錄insert into新表,在copy階段,此時新表已經有這條記錄了,但是採用的是insert ignore操作,因此會忽略掉該條記錄,

因此原表和新表數據還是一致的。

對於delete語句,不管是先copy還是先delete再copy,都不會導致數據不一致。

也許你會說,如果在cop階段,執行了DML操作,會不會導致數據不一致呢,答案是不會。在copy階段,pt-osc會根據

主鍵或者唯一鍵來拆分chunk來執行copy操作,對於每一個chunk,採用的是insert ignore xxxx select xxx lock in share mode。

lock in share mode,會堵塞該chunk的其他dml操作,保證該chunk上記錄操作的串行性,因此此時如果用戶操作該chunk的記錄,

其實是會堵塞的,只有copy完成了,用戶操作才能繼續,則又回到上面流程上了,這樣保證每一個chunk操作的一致性。

因此copy階段,也不會導致數據不一致。

這就是爲何pt-osc一定要有主鍵或者唯一鍵的原因,爲了保證原表和新表數據的一致性。

下面說說 pt-osc chunk拆分方式:

默認chunk-size 1000,如果小於一個chunk ,則整表按照一個chunk來處理,否則則按chunk size來拆分,如果沒有指定chunk-size 默認爲1000,

但是並不是每個chunk按照該大小來拆分,pt-osc會根據每個上次chunk的處理時間和chunk大小來計算下一個chunk的大小,以保證處理的速度,

如果指定了chunk-size 則按照指定的chunk size來拆分。

   # Explicit --chunk-size disable auto chunk sizing.

   $o->set('chunk-time', 0) if $o->got('chunk-size');   如果指定了chunk-size參數大小,則將chunk-time設置爲0,後續再計算chunk size的時候,會用該chunk-time

  my $chunk_time = $o->get('chunk-time');        # brevity   這裏獲取到chunk-time 爲0

  # Adjust chunk size.  This affects the next chunk.   計算下一次操作的chunk size

            if ( $chunk_time ) {    如果指定chunk-size,則chunk_time爲0,則每個chunk的大小有chunk-size決定,否則則是根據上次操作的時間和chunk size來計算下次chunk 的大小

               # Calcuate a new chunk-size based on the rate of rows/s.

               $tbl->{chunk_size} = $tbl->{rate}->update(

                  $cnt,                # processed this many rows

                  $tbl->{nibble_time}, # is this amount of time

               );

               if ( $tbl->{chunk_size} < 1 ) {

                  # This shouldn't happen.  WeightedAvgRate::update() may

                  # return a value < 1, but minimum chunk size is 1.

                  $tbl->{chunk_size} = 1;

                  # This warning is printed once per table.

                  if ( !$tbl->{warned_slow} ) {

                     warn ts("Rows are copying very slowly.  "

                        . "--chunk-size has been automatically reduced to 1.  "

                        . "Check that the server is not being overloaded, "

                        . "or increase --chunk-time.  The last chunk "

                        . "selected $cnt rows and took "

                        . sprintf('%.3f', $tbl->{nibble_time})

                        . " seconds to execute.\n");

                     $tbl->{warned_slow} = 1;

                  }

               }

               # Update chunk-size based on the rate of rows/s.

               $nibble_iter->set_chunk_size($tbl->{chunk_size});

            }

# pt_online_schema_change:10809 118966 SHOW WARNINGS

# Retry:3762 118966 Try code succeeded

# pt_online_schema_change:9335 118966 Nibble time: 0.526419878005981 上個chunk的處理時間

# NibbleIterator:5538 118966 0 rows in nibble 8

# NibbleIterator:5550 118966 No rows in nibble or nibble skipped

# pt_online_schema_change:9358 118966 Average copy rate (rows/s): 13159

# WeightedAvgRate:5256 118966 Master op time: 6728 n / 0.526419878005981 s

# WeightedAvgRate:5262 118966 Weighted avg rate: 13249.0307387304 n/s    計算平均速度

# WeightedAvgRate:5272 118966 Adjust n to 6624    設置限制爲平均速度的  / 2

# NibbleIterator:5634 118966 Set new chunk size (LIMIT): 6624

# ReplicaLagWaiter:4897 118966 All slaves caught up

# MySQLStatusWaiter:5145 118966 Checking status variables

# pt_online_schema_change:8502 118966 SHOW GLOBAL STATUS LIKE ? Threads_running

# MySQLStatusWaiter:5148 118966 Threads_running = 1

# MySQLStatusWaiter:5175 118966 All var vals are low enough

# pt_online_schema_change:10872 118966 EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`online_test` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) ORDER BY `id` LIMIT ?, 2 /*next chunk boundary*/ params: 024b8b98a545fc23cf6b832155644c1e 6623

如果指定了chunk-size的值,則按照每個chunk size來拆分,每個chunk 數量size - 1,而不再上每次計算下次chunk size大小

# Retry:3745 3029 Try 1 of 10

# pt_online_schema_change:10790 3029 INSERT LOW_PRIORITY IGNORE INTO `test`.`_______online_test_new` (`id`, `c1`, `c2`, `c3`, `c4`, `c5`, `c8`) SELECT `id`, `c1`, `c2`, `c3`, `c4`, `c5`, `c8` FROM `test`.`online_test` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) AND ((`id` <= ?)) LOCK IN SHARE MODE /*pt-online-schema-change 3029 copy nibble*/ lower boundary: 444501 upper boundary: 445000

# pt_online_schema_change:10809 3029 SHOW WARNINGS

# Retry:3762 3029 Try code succeeded

# pt_online_schema_change:9335 3029 Nibble time: 0.0557880401611328

# NibbleIterator:5538 3029 0 rows in nibble 890

# NibbleIterator:5550 3029 No rows in nibble or nibble skipped

# pt_online_schema_change:9358 3029 Average copy rate (rows/s): 8484

# ReplicaLagWaiter:4897 3029 All slaves caught up

# MySQLStatusWaiter:5145 3029 Checking status variables

# pt_online_schema_change:8502 3029 SHOW GLOBAL STATUS LIKE ? Threads_running

# MySQLStatusWaiter:5148 3029 Threads_running = 1

# MySQLStatusWaiter:5175 3029 All var vals are low enough

# pt_online_schema_change:10872 3029 EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`online_test` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) ORDER BY `id` LIMIT ?, 2 /*next chunk boundary*/ params: 445001 499  

有了chunk size,pt-osc採用的是 select id from xxx  where id >= ? order by id limit  (chunk_size -1),2 獲取到每次操作chunk的最大id範圍,注意這裏的id可以是主鍵,也可以是唯一鍵。

這裏得到兩個值,第一個值是此次chunk操作的最大值,第二個值則是下一個chunk的最小值,對應下次的where id >=?

得到一個具體的id值,然後採用INSERT LOW_PRIORITY IGNORE  xxx FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) AND ((`id` <= ?)) LOCK IN SHARE MODE 來copy數據,

where條件最該該chunk的最大值和最小值,最大值是前面select獲取到的,而最小值是每次chunk操作完設置的,也是上面select查詢得到的。按照這樣迭代,根據主鍵或者唯一鍵升順一個一個chunk的來操作。

另外不只是pt-osc chunk的拆分是這樣方式,實際上pt-table-checksum也是按照這種方式來拆分的。

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