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出现了数据不一致
  • 这个Bug的修复方法见

    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一定是完整的事务