MySQL5.5升级5.7步骤和升级步骤bug解析
背景
在使用平台将一个数据库从5.5升级到5.7的时候,发现刚升级从库出现了复制中断,一开始出现的是update找不到对应的行,重复升级几次,都会有不同的报错,整体就是从库升级之后数据和主库不一致了,导致有一些binlog没法执行。
更加严重的问题是,一些对历史数据修改不会造成主从同步中断的情况,这种升级方式,很难发现主从数据不一致。
升级步骤
0.从库挂载
在 mysql 5.5.39 的库上做备份,并且扩容一个库,以5.5的版本启动数据库。
1.mysqldb.stop_mysql
关闭5.5版本的 mysql instance。
2.mysqldb.rsync_cnf
获取 my.cnf 模版并替换其中的数据。修改和添加一些5.7版本的特定参数。
3.mysqldb.comment_cnf(comment=1)
为了使用 mysql 5.7 的 bin 启动 5.5 的文件,需要修改配置模版。修改后的行:
#master_info_repository = TABLE
#relay_log_info_repository = TABLE
#gtid_mode = on
4.mysqldb.build_mysql
使用5.7 启动 mysql instance 。
5.mysqldb.upgrade
升级 mysql 文件。
mysql_upgrade
start slave; (这一步必须执行,让mysql重新拉取升级之前没有执行的binlog)
stop slave;(刷新master.info)
之后查看master info文件,正确之后再次执行如步骤
set global master_info_repository = 'TABLE';
set global relay_log_info_repository = 'TABLE';
set global read_only = 1;
set global slave_parallel_workers = 0;
start slave; (备份实例,新5.7的实例可以在此基础上扩容)
6.mysqldb.comment_cnf (comment = 0)
恢复第三步的修改。修改后的行:
master_info_repository = TABLE
relay_log_info_repository = TABLE
gtid_mode = on
7. 读切上线
8. 写切上线,并且升级GTID
升级到gtid步骤、所有的操作从最下层库一直到最上层库,多注意观察errlog日志。
1)、所有的Server执行
set@@global.enforce_gtid_consistency = warn;
特别注意: 这一步是关键的一步使用不能出现警告。
2)、所有的server上执行:
set@@global.enforce_gtid_consistency = on;
3)、所有的Server上执行:
set@@global.gtid_mode = off_permissive;
4)、所有的Server上执行:
set@@global.gtid_mode=on_permissive;
实质在这一步骤生成的日志都是带GTID的日志了,这个步骤号称是不关心任何节点,但从实际管理上推荐在slave上先执行,然后再去master上执行。
9. 传统的binlog格式会产生一个匿名的事务,所以需要确认复制完成
show status like 'ongoing_anonymous_transaction_count';
所有的节点都确认为0,并且查看binlog备份是否到最新的binlog后,才可以进行下一步。
可以flush logs一次进行日志切换,不操作也可以。
10. 所有的节点启用gtid_mode
set@@global.gtid_mode=on;
11. 更新配置文件,把gtid_mode=on前面的注释打开
12. 启用Gtid的自动复制:
stop slave;change master to master_auto_position=1;start slave;
13.配置mha
14.配置备份
如上的升级步骤中,第5步标红的是之前平台升级步骤中没有的,按照如上的步骤执行升级,就会遇到我们背景中的问题,也就是部分的binlog丢失。
故障技术分析
1、在使用新版本MySQL启动旧版本MySQL实例时,会将io线程拉取binlog的位点重置为sql线程已经执行到的位点,当开始主从同步后,就会将sql线程未执行的binlog重新拉取到从库执行,因此不会发生数据丢失的情况
2、MySQL5.7为了提升数据一致性,提供使用innodb表记录同步位点的功能,命令为:set global master_info_repository = 'TABLE';
3、数据库自动化运维平台的升级流程如下:
使用5.7启动5.5实例,进行upgrade(此时如描述1,io线程拉取位点被重置成sql线程的位点)
执行set global master_info_repository = 'TABLE'; (此时MySQL读取master.info中记录的位点信息,写到同步位点表中,结果将步骤1中io线程的位点又重置成之前io线程已经拉取到的位点)
4、因此当执行upgrade的实例存在已经拉取到本地,且未执行的relay.log的时候,通过平台升级5.7,将发生数据丢失,丢失的范围是已经拉取到从库,且未被sql线程执行的事务。
5、为什么我们背景中提到的实例会发现这样的问题,是因为这个实例对于数据的同一行的更新比较多,要是有数据的不一致很快就会发现。但是也有数据库对于历史的数据是没有操作的,丢失一段数据很难发现,就会造成一段数据丢失并且发现周期很长。
测试升级过程
第一步:模拟升级的步骤,备份扩容数据库
第二步:压测产生很多binlog,从库在延迟很多的时候升级
手动的执行一个表的insert update delete操作,并且从库还没有执行到的时候,进行关闭
show slave status 关键信息展示:
Master_Log_File: xx-bin.000020
Read_Master_Log_Pos: 276172003
Relay_Log_File: xx-relay.000021
Relay_Log_Pos: 558932832
Relay_Master_Log_File: xx-bin.000008
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Exec_Master_Log_Pos: 770236470
Relay_Log_Space: 13164300091
记录:
xx-bin.000020
276172003
第三步:先关闭实例,之后重启(修改配置文件),记录show slave status 结果
重启之后的show,又变回到执行到的位置了(截止这一步是正确的)
show slave status 关键信息展示:
Master_Log_File: xx-bin.000008
Read_Master_Log_Pos: 865139657
Relay_Log_File: xx-relay.000057
Relay_Log_Pos: 4
Relay_Master_Log_File: xx-bin.000008
Exec_Master_Log_Pos: 865139657
Relay_Log_Space: 13164300264
第四步:执行升级
第五步:之后set,查看slave的信息:(这一步把master.info的位置重置到show slave status 上了,发生了错误)
注:正确的步骤是这一步需要先执行start slave;stop slave ;查看master.info里面的值是不是正确;也就是升级步骤中第5步标红的部分。
set global master_info_repository = 'TABLE’;
#将master.info的数据刷新到表
set global relay_log_info_repository = 'TABLE’;
#这个之后master.info和relay.info的文件直接删除
show slave status 关键信息展示:
Master_Log_File: xx-bin.000020
Read_Master_Log_Pos: 276172003
Relay_Log_File: xx-relay.000057
Relay_Log_Pos: 4
Relay_Master_Log_File: xx-bin.000008
Exec_Master_Log_Pos: 865139657
Relay_Log_Space: 13164300264
第六步:start slave;
set 之后binlog从master.info的地方拉了,并且是从这个地方执行了,不再去执行已经拉取到的relay了,一下子就跳变到新的位置了(直接跳过老的relaylog)。
show slave status 关键信息展示:
Master_Log_File: xx-bin.000020
Read_Master_Log_Pos: 276172003
Relay_Log_File: xx-relay.000058
Relay_Log_Pos: 301
Relay_Master_Log_File: xx-bin.000020
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Exec_Master_Log_Pos: 276172003
Relay_Log_Space: 501
总结
1、一开始的升级步骤,要是扩容的从库没有延迟,直接set global master_info_repository = 'TABLE’; 这个也会正常的执行,没有数据丢失。也就是已执行和已经拉取到的位点一致。关键点还是master.info里面记录的位点信息和实际执行到的位点信息是否一致的,如果master.info的位点超前了,这一步肯定会出错。
2、5.5到5.7版本的升级其实建议是mysqldump 逻辑数据导入,但是文中的升级步骤经过严格的测试也没有问题,可以推荐使用。
3、升级还是需要做数据的一致性校验,文中在第6步之后应该再加一个主从数据的校验。因为有一些操作复制不中断,也不能保证数据的一致性。线上的数据库,即使是不升级,也建议有定期的主从数据一致性校验的机制。
备注
enforce_gtid_consistency:默认off,可选值有on和warn。根据该变量的值,服务器只允许可以安全使用GTID记录的语句通过,强制GTID一致性。在启用基于GTID复制之前将此变量需要设置为on。
OFF :不检测是否有GTID不支持的语句和事务。
Warn :当检测到不支持GTID的语句和事务,返回警告,并在日志中记录。
ON :当检测到不支持GTID的语句和事务,返回错误。
gtid_mode:控制是否开启GTID,默认OFF。可选值有OFF、OFF_PERMISSIVE、ON、ON_PERMISSIVE。
OFF :不产生GTID,Slave只接受不带GTID的事务
OFF_PERMISSIVE :不产生GTID,Slave即接受不带GTID的事务,也接受带GTID的事务
ON_PERMISSIVE :产生GTID,Slave即接受不带GTID的事务,也接受带GTID的事务
ON :产生GTID,Slave只能接受带GTID的事务。