vlambda博客
学习文章列表

MySQL双写情况下复制update/delete的同步问题

背景

架构介绍:

机房1主库

数据流向

机房2主库

备注

M1

——>

M2

M2单向同步了M1的数据

为了完成机房1到机房2的迁移,主库写入点切换到M2,但是还不能一把切,只能两边都有数据流量(也就是双写),需要测试什么情况下会造成数据不一致或者同步中断的情况。

测试环境:

MySQL版本

5.7.26

binlog_format

row

GTID_MODE

on


第一种情况:表结构中带有主键id的情况

CREATE TABLE `test` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `ustatus` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8


测试一【测试update的执行】:

m2单向同步m1的数据(m1的数据同步到m2,这里m2是从库)

update之前:


| id | ustatus |+----+---------+| 1 | 0 || 3 | 1 || 5 | 2 || 7 | 3 |

m2上执行(从库先执行update):update test set ustatus=5 where id=1【将之前id=1  ustatus=0 更新为ustatus=5】

### UPDATE `br_test`.`test`### WHERE### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */### @2=0 /* INT meta=0 nullable=0 is_null=0 */### SET### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */### @2=5 /* INT meta=0 nullable=0 is_null=0 */

m1上执行(主库后执行):update test set ustatus=4 where id=1【将之前id=1  ustatus=0 更新为ustatus=4】

主库上的binlog:


### UPDATE `br_test`.`test`### WHERE### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */### @2=0 /* INT meta=0 nullable=0 is_null=0 */【带有本实例的原值】### SET### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */### @2=4 /* INT meta=0 nullable=0 is_null=0 */

m2从库上的binlog显示:

update test set ustatus=4 where id=1### UPDATE `br_test`.`test`### WHERE### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */### @2=5 /* INT meta=0 nullable=0 is_null=0 */【带有本实例的原值】### SET### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */### @2=4 /* INT meta=0 nullable=0 is_null=0 */
+----+---------+| id | ustatus |+----+---------+| 1 | 4 || 3 | 1 || 5 | 2 || 7 | 3 |


如上的执行,最终数据是一致的,数据更新是按照原条件更新的,和原有数据行主从的数据是否一致没关系

此时如果将m1的主库指向m2上,则m1上的数据会被m2上之前的update test set ustatus=5 where id=1覆盖, 覆盖掉id=1 ustatus=4这个记录


主库记录:

+----+---------+| id | ustatus |+----+---------+| 1 | 5 || 3 | 1 || 5 | 2 || 7 | 3 |

从库:

+----+---------+| id | ustatus |+----+---------+| 1 | 4 || 3 | 1 || 5 | 2 || 7 | 3 |

导致主从数据不一致


测试步骤和数据显示:


m2(从库)

m1(主库)

状态

m2:update test set ustatus=5 where id=1

1,5

1,0

主从数据不一致

m1:update test set ustatus=4 where id=1

1,4(同步了m1的update)

1,4

主从数据一致

m1同步m2的数据:m1执行change master to m2

1,4

1,5

主从数据不一致


测试二:【测试delete】

#初始化主从数据+----+---------+| id | ustatus |+----+---------+| 1 | 11 || 3 | 1 || 5 | 2 || 7 | 3 |

步骤一:从库执行:update test set ustatus=10 where  ustatus=11;

+----+---------+| id | ustatus |+----+---------+| 1 | 10 || 3 | 1 || 5 | 2 || 7 | 3 |

主库数据:

+----+---------+| id | ustatus |+----+---------+| 1 | 11 || 3 | 1 || 5 | 2 || 7 | 3 |

步骤二:主库执行:delete from  test where id=1;

从库上binlog:


### DELETE FROM `br_test`.`test`### WHERE### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */### @2=10 /* INT meta=0 nullable=0 is_null=0 */

主库上binlog:

### DELETE FROM `br_test`.`test`### WHERE### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */### @2=11 /* INT meta=0 nullable=0 is_null=0 */

此时主从数据是一致的, id=1的记录被删除了

#主从数据显示

+----+---------+| id | ustatus |+----+---------+| 3 | 1 || 5 | 2 || 7 | 3 |+----+---------+

步骤三:将m1的主库指向m2上

则m1上会执行m2上之前执行的语句update test set ustatus=10 where  ustatus=11;

但是此时m1上的id=1 ustatus=11的数据早被删除了 则m1没法正常的同步;

会出现报错:br_test.test; Can't find record in 'test', Error_code: 1032;


测试步骤和数据显示:


m2(从库)

m1(主库)

状态

m2: update test set ustatus=10 where  ustatus=11;

1,10

1,11

主从数据不一致

m1:delete from  test where id=1;

id=1的数据被删除

id=1的数据被删除

主从数据一致

m1同步m2的数据:m1执行change master to m2


br_test.test; Can't find record in 'test', Error_code: 1032;

主从数据还是一致,但是同步中断,没法执行update


第二种情况:表结构中不带有主键的情况

CREATE TABLE `test2` ( `id` bigint(20) unsigned NOT NULL, `ustatus` int(10) unsigned NOT NULL DEFAULT '0') ENGINE=InnoDB DEFAULT CHARSET=utf8
+----+---------+| id | ustatus |+----+---------+| 1 | 1 || 2 | 2 || 3 | 3 |


第一步:从库上执行update test2 set ustatus=10 where ustatus=1;

### DELETE FROM `br_test`.`test2`### WHERE### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */### @2=1 /* INT meta=0 nullable=0 is_null=0 */

从库:

+----+---------+| id | ustatus |+----+---------+| 1 | 10 || 2 | 2 || 3 | 3 |

主库:

+----+---------+| id | ustatus |+----+---------+| 1 | 1 || 2 | 2 || 3 | 3 |


第二步:主库上执行delete from test2 where id=1;

此时从库同步停止br_test.test2; Can't find record in 'test2', Error_code: 1032

没有主键的情况,从库上操作直接比对的是全部列的数据


测试步骤和数据显示:


m2(从库)

m1(主库)

状态

m2: update test2 set ustatus=10 where ustatus=1;

1,10

1,1

主从数据不一致

m1:delete from test2 where id=1;

同步中断

id=1的数据被删除

主从数据不一致


结论:

任何情况下,不要在从库直接执行数据行的变更,否则会导致不可预知的错误,数据一致性很可能被破坏。如果非要在从库执行事务,就要想好办法应对下面的问题。


第一:当出现同步中断的时候,如果主从数据是一致的,那么就可以执行skip,如上面的带有主键的delete,M1上的同步中断是可以跳过的,最终数据一致。

第二:当没有出现主从同步断开的情况,这时也不能保证数据是一致的,需要校验主从数据一致性。


所以,正常情况下,对于主从数据一致性要求极高的库来说,不建议从库执行事务,在主库维护需要切换主从并且有双写的情况下,一定要校验双写期间的数据变更次序(因为如稳重带有主键id的update,虽然没有出现主从同步中断的情况,但是数据一致性已经遭到破坏)