线上环境复制使用ROW模式,对于上亿的表,使用pt online schema change 在把数据从旧表拷贝到临时表这步操作,会产生大量的binlog,这会导致主从延迟
在pt工具包2.1之前,pt-online-schema-change是不会打印binlog的,如果要在主从上加索引,需要分别在主库执行一次,在从库执行一次
它提供了一个--log-bin产生,并且默认是关闭的
--bin-log
Allow binary logging (SET SQL_LOG_BIN=1). By default binary logging is turned off because in most cases the --tmp-tabledoes not need to be replicated.
而在pt工具2.2版本以后,会默认打binlog,好处是在不用分别在各个节点执行一次改表操作,只需要在主库执行一次改表,就会通过binlog让下面的从库的表都被修改
pt工具3.0版本,有一个 --set-vars='sql_log_bin=0' 参数能取代 --bin-log=0 效果
刚好有一个1.5亿表加索引的需求,就使用如下命令,1.5亿生成的binlog预计会有20G,为了不产生binlog,准备在每个点执行一次,先在主库执行
pt-online-schema-change --host=主机 --port=端口号 --user=节点号 --database=数据库名 t=t_room_impeach --alter="ADD INDEX idx_psr(A,B,C)" --set-vars='sql_log_bin=0' --execute
这条语句一下去,主库下面的4个从库同步都中断了,show slave status报错
Last_SQL_Errno: 1146 Last_SQL_Error: Error executing row event: 'Table 'live_oss._t_room_impeach_new' doesn't exist'
报错_t_room_impeach_new表存在,为什么这张表要存在呢?
posc工具的原理是,先创建一个临时表,表名是 _原来的表名_new,这张临时表是已经加入了你想要的索引,不停把旧表的数据拷贝到这张临时表,新插入,修改,删除的旧表的数据,都会根据触发器,同样新插入,修改,删除到临时表,等拷贝数据,旧表和临时表就是一模一样了,这个时候把临时表rename成为就表的名字,而实际的旧表就会被drop掉,在线完成
当主库执行命令是会显示创建临时表,创建触发器
Creating new table... Created new table live_oss._t_room_impeach_new OK. Altering new table... Altered `live_oss`.`__t_room_impeach_new` OK. 2017-08-02T16:38:48 Creating triggers... 2017-08-02T16:38:48 Created triggers OK. 2017-08-02T16:38:48 Copying approximately 141559863 rows...
因为 --set-vars='sql_log_bin=0'的原因,创建表的DDL语句,无法通过binlog在从库建表,所以从库是表不存在的,问题是从库不需要存在临时表啊,因为只操作主库一个点就足够了
这个是posc第一个坑,主库因触发器触发器产生的数据,会产生binlog,从而同步到从库,当从库要执行这些数据时,发现表不存在,导致同步中断
这时解决方法是在从库,去建立同样一张临时表 _xxxx_new,好让触发器的数据,能够顺利插入到这张表,当建了以后可以看到从库的临时表有数据了,再次验证sql_log_bin=0没有效果
explain select count(*) from __t_room_impeach_new; +----+-------------+----------------------+-------+---------------+------+---------+------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------------------+-------+---------------+------+---------+------+----------+-------------+ | 1 | SIMPLE | __t_room_impeach_new | index | NULL | uid | 4 | NULL | 176| Using index | +----+-------------+----------------------+-------+---------------+------+---------+------+----------+-------------+
几个从库都有176条数据,再看看主库的临时表,有差不多1亿数据,因为除了触发器还有来自旧表的
explain select count(*) from __t_room_impeach_new; +----+-------------+----------------------+-------+---------------+------+---------+------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------------------+-------+---------------+------+---------+------+----------+-------------+ | 1 | SIMPLE | __t_room_impeach_new | index | NULL | uid | 4 | NULL | 10527757 | Using index | +----+-------------+----------------------+-------+---------------+------+---------+------+----------+-------------
当时有个担心
主库临时表 __t_room_impeach_new 数据 = 触发器产生数据 + 旧表产生数据
从库临时表 __t_room_impeach_new数据 = 触发器产生的数据
如果所有点执行最后一步操作 rename 临时表__t_room_impeach_new to t_room_impeach 正式表,岂不是主从数据不一致,从库少了很多数据?
不过按道理这种情况不会发生,因为--set-vars='sql_log_bin=0'会把rename这个DDL语句,像create table一样给阻隔掉,不会导致从库改表成功
为了不冒险,打算重新执行一次,这次加入2个参数,
--no-drop-old-table 即使执行完了命令,也不要drop表,让我确认旧表新表是一致的再手动drop
--no-drop-triggers 触发器也保留
执行命令之前,先把临时表,触发器都手动删除,正如提示说的
Not dropping triggers because the tool was interrupted. To drop the triggers, execute: DROP TRIGGER IF EXISTS `live_oss`.`pt_osc_live_oss_t_room_impeach_del`; DROP TRIGGER IF EXISTS `live_oss`.`pt_osc_live_oss_t_room_impeach_upd`; DROP TRIGGER IF EXISTS `live_oss`.`pt_osc_live_oss_t_room_impeach_ins`; Not dropping the new table `live_oss`.`_t_room_impeach_new` because the tool was interrupted. To drop the new table, execute: DROP TABLE IF EXISTS `live_oss`.`_t_room_impeach_new`; `live_oss`.`t_room_impeach` was not altered.
另外还有在从库先把临时表建立起来,这次执行到一半的时候,4个从库又报错,同步中断了
Last_SQL_Errno: 1032
Last_SQL_Error: Could not execute Update_rows event on table live_oss._t_room_impeach_new; Can't find record in '_t_room_impeach_new', Error_code: 1032; handler error HA_ERR_KEY_NOT_FOUND; the event's master log mysql-bin.056637, end_log_pos 41767716
这次的报错是update语句失败了,row模式的update语句是 set 新值 where 旧值 ,如果在从库的临时表上,找不到旧值,就会报这样的错
同样因为--set-vars='sql_log_bin=0',导致从库临时表,比主库临时表少很多数据,所以很可能一条update语句下来,就会因为找不到数据而中断
总结-在myqsl主从复制下,主库不要用这种模式,因为无法阻止触发器带来binlog,仅仅在从库执行时可以的
另外如果使用--no-drop-old-table和--no-drop-triggers参数,最终结果是命令到99%一直卡住
Copying `live_oss`.`t_room_impeach`: 99% 01:01 remain
Copying `live_oss`.`t_room_impeach`: 99% 00:47 remain
Copying `live_oss`.`t_room_impeach`: 99% 00:35 remain
Copying `live_oss`.`t_room_impeach`: 99% 00:21 remain
Copying `live_oss`.`t_room_impeach`: 99% 00:09 remain
还有一个坑就是业务时不时会反馈一吧报错,一张临时表不存在,但这张临时表应该是对业务透明才对的,真心奇怪
XXXX 说: (17:19:49)
Base table or view not found: 1146 Table 'live_oss.__t_room_impeach_new' doesn't exist
报了这个错误的
xxxx 说: (17:21:00)
怎么表名变为t_room_impeach_new了
XXXXXX 说: (17:25:27)
原来的表还存在把
XXXXX说: (17:25:47)
现在又恢复了