pt-online-schema-change|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 记录到新表,这样就导致新表多出来
【pt-online-schema-change|pt-online-schema-change 原理简单介绍】一条记录,若该记录更新较多,则会在新表插入多条记录,从而导致数据不一致。对于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 xxxwhere id >= ? order by id limit(chunk_size -1),2 获取到每次操作chunk的最大id范围,注意这里的id可以是主键,也可以是唯一键。
这里得到两个值,第一个值是此次chunk操作的最大值,第二个值则是下一个chunk的最小值,对应下次的where id >=?
得到一个具体的id值,然后采用INSERT LOW_PRIORITY IGNORExxx FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) AND ((`id` <= ?)) LOCK IN SHARE MODE 来copy数据,
where条件最该该chunk的最大值和最小值,最大值是前面select获取到的,而最小值是每次chunk操作完设置的,也是上面select查询得到的。按照这样迭代,根据主键或者唯一键升顺一个一个chunk的来操作。
另外不只是pt-osc chunk的拆分是这样方式,实际上pt-table-checksum也是按照这种方式来拆分的。

    推荐阅读