MySQL < 5.6.15
在Master/Slave复制模型下:
- 【1】Slave收到Gtid_log_event就会把GTID记录到Retrieved_Gtid_Set
- 【2】如果此时Master突然宕机/网络断开等,Slave收到的这个事务并不完整,记为last_retrieved_gtid
- 【3】Slave执行此事务时,由于失败会回滚,因此Executed_Gtid_Set并不会记录这个GTID
【4】当Master恢复后或者Slave指向一个新的Master
- 【4.1】Slave会把Slave_Gtid_Set = Retrieved_Gtid_Set + Executed_Gtid_Set的并集发送给Master
- 【4.2】Master将自身的Executed_Gtid_Set - Slave_Gtid_Set的差集发送给Slave(Master认为Slave没有执行这些GTIDs)
- 【4.3】因为last_retrieved_gtid包含在Slave_Gtid_Set中(Slave的Retrieved_Gtid_Set中)
- 【4.4】所以Slave不会再收到这个last_retrieved_gtid
- 【4.5】Master/Slave上很有可能造成了数据不一致
5.6.15 <= MySQL < 5.7.5
MySQL 5.6.15加入了这个patch
在 “MySQL < 5.6.15”【4.1】中,Slave做如下检查:
【4.1.1】如果last_retrieved_gtid包含在Retrieved_Gtid_Set中,Slave认为这个GTID执行过,发送给Master的集合如下:
- Slave_Gtid_Set = (Retrieved_Gtid_Set - last_retrieved_gtid)+ Executed_Gtid_Set
- 【4.1.2】如果last_retrieved_gtid不包含在Retrieved_Gtid_Set中,Slave认为这个GTID没有执行过,发送给Master的集合如下:
- Slave_Gtid_Set = Retrieved_Gtid_Set + Executed_Gtid_Set
咋看起来没问题,但是会产生Bug #72392,根本原因在于:
“如果last_retrieved_gtid不包含在Retrieved_Gtid_Set中,Slave认为这个GTID没有执行过”是错误的
Bug #72392
【场景】
Master/Slave使用GTID复制
【原因分析】
在Slave上执行RESET MASTER会导致:Executed_Gtid_Set清空
- Retrieved_Gtid_Set不变
那么继续START SLAVE时:
- Slave的Retrieved_Gtid_Set中的last_retrieved_gtid ( insert into t1 values(4,4) )并不包含在Executed_Gtid_Set(因为Executed_Gtid_Set为空)
- Slave会将(Retrieved_Gtid_Set - last_retrieved_gtid)+ Executed_Gtid_Set发送给Master
- Master会将last_retrieved_gtid再次发送过来
- 因为Executed_Gtid_Set为空,所以Slave会再次执行last_retrieved_gtid ( insert into t1 values(4,4) )
- 因此Master/Slave出现了数据不一致
-
MySQL >= 5.7.6
在MySQL 5.7.6中加入了这个patch,加入了GTID事务的“边界检测功能”(Transaction Boundary Parser)
在 “MySQL < 5.6.15”【1】中 Slave收到Gtid_log_event时不会立即加入Retrieved_Gtid_Set
- 使用Transaction Boundary Parser,只有当I/O线程收到完整的GTID事务,才会将这个GTID添加到Retrieved_Gtid_Set
- 因此即使Master出现突然宕机等情况,也能保证Retrieved_Gtid_Set的last_retrieved_gtid一定是完整的事务