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也是按照这种方式来拆分的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容