这里分享一个MySQL主从库因为某张表数据不一致的解决方法,我们可能会遇到这种情况:实例A和B是主从,可能由于误操作或者某种原因导致A和B某张表数据不一致,可能就会导致主从复制停止,这时我们有很多种解决办法,比如忽略掉某个错误,跳过events,设置slave_exec_mode为IDEMPOTENT,重做从库,这些方法虽然能够恢复主从运行,但是会导致数据不一致或者恢复成本较大,当然你也可以使用pt-table-sync修复,这里提供一种利用pt-osc重做表的方法来恢复主从并解决一致性问题。
【模拟主从不一致场景】
表结构
show create table tb_info\G
*************************** 1. row ***************************
Table: tb_info
Create Table: CREATE TABLE `tb_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`city` char(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
主库上表数据
mysql> select * from tb_info;
+----+----------+
| id | city |
+----+----------+
| 1 | beijing |
| 2 | shanghai |
| 3 | shenzhen |
+----+----------+
从库上表数据
mysql> select * from tb_info;
+----+----------+
| id | city |
+----+----------+
| 1 | beijing |
| 2 | shanghai |
| 3 | shenzhen |
+----+----------+
在从库上删除一条数据
delete from tb_info where id=3;
在主库上删除一条数据
delete from tb_info where id=3;
在从库上查看主从状态
Last_Errno: 1032
Last_Error: Could not execute Delete_rows event on table db_sms.tb_info; Can't find record in 'tb_info', Error_code: 1032; handler error HA_ERR_KEY_NOT_FOUND; the event's master log mysql-bin-83-3306.000018, end_log_pos 4868
主从同步已经停止,因为在主库上删除id为3的数据时,binlog传到从库执行时没找到这条数据,所以发生了错误。
【解决方法】
下面来看如何恢复主从且让数据达到一致性
先让复制运行起来
在从库上操作
stop slave;
set global slave_exec_mode='IDEMPOTENT';
start slave;
show slave status\G
在主上使用pt-osc重做表
pt-online-schema-change -h 127.0.0.1 -u root -P 3306 --alter "engine=innodb" D=db_sms,t=tb_info --charset=utf8 --execute --nocheck-replication-filter
在从库查看复制状态
show slave status\G
验证tb_info表数据是否一致
在主库上使用pt工具进行主从数据检测
pt-table-checksum --nocheck-replication-filters --no-check-binlog-format --recursion-method=processlist --replicate=test.checksum --databases=db_sms h=127.0.0.1,u=root,p='123456',P=3306
select * from test.checksum where this_crc <> master_crc or this_cnt <> master_cnt\G
确认主从一致,最后把slave_exec_mode修改为默认的STRICT
set global slave_exec_mode='STRICT';
【原理】
我们主要利用了pt-osc建立一个影子表的原理,这里我们并没有修改表结构,只是把主库上的tb_info表重做了一遍,然后复制到从库,从而达到数据一致的目的。
下面是pt-osc的工作原理,详细原理和信息请参考官方网站
<1>创建一个和要执行alter操作的表一样的新的空表结构(是alter之前的结构)
<2>在新表执行alter table语句
<3>在原表中创建触发器3个触发器分别对应insert,update,delete操作
<4>以一定块大小从原表拷贝数据到临时表,拷贝过程中通过原表上的触发器在原表进行的写操作都会更新到新建的临时表
<5>Rename原表到old表中,在把临时表Rename为原表
<6>如果有参考该表的外键,根据alter-foreign-keys-method参数的值,检测外键相关的表,做相应设置的处理
<7>默认最后将旧原表删除
下面是在主库上解析的binlog
# at 6251
#170407 11:00:47 server id 83330601 end_log_pos 6311 CRC32 0x5de3f8ed Table_map: `db_sms`.`_tb_info_new` mapped to number 111
# at 6311
#170407 11:00:47 server id 83330601 end_log_pos 6373 CRC32 0x66de3a8d Write_rows: table id 111 flags: STMT_END_F
BINLOG '
3wDnWBMphvcEPAAAAKcYAAAAAG8AAAAAAAEABmRiX3NtcwAMX3RiX2luZm9fbmV3AAID/gL+HgLt
+ONd
3wDnWB4phvcEPgAAAOUYAAAAAG8AAAAAAAEAAgAC//wBAAAAB2JlaWppbmf8AgAAAAhzaGFuZ2hh
aY063mY=
'/*!*/;
### INSERT INTO `db_sms`.`_tb_info_new`
### SET
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2='beijing' /* STRING(30) meta=65054 nullable=1 is_null=0 */
### INSERT INTO `db_sms`.`_tb_info_new`
### SET
### @1=2 /* INT meta=0 nullable=0 is_null=0 */
### @2='shanghai' /* STRING(30) meta=65054 nullable=1 is_null=0 */
# at 6373
#170407 11:00:47 server id 83330601 end_log_pos 6404 CRC32 0x98c172ae Xid = 347
COMMIT/*!*/;
# at 6404
#170407 11:00:47 server id 83330601 end_log_pos 6540 CRC32 0xfaa3e0ad Query thread_id=21 exec_time=0 error_code=0
SET TIMESTAMP=1491534047/*!*/;
ANALYZE TABLE `db_sms`.`_tb_info_new` /* pt-online-schema-change */
/*!*/;
# at 6540
#170407 11:00:47 server id 83330601 end_log_pos 6723 CRC32 0xd5ba4074 Query thread_id=21 exec_time=0 error_code=0
SET TIMESTAMP=1491534047/*!*/;
RENAME TABLE `db_sms`.`tb_info` TO `db_sms`.`_tb_info_old`, `db_sms`.`_tb_info_new` TO `db_sms`.`tb_info`