主从数据不一致竟然不报错?

本文讲述了作者在MySQL5.7.35环境下遇到的线上数据丢失故障,故障表现为主从数据不一致但复制状态正常。问题源于从库GTID值大于主库,通过设置Auto_Position=1解决,强调了在GTID模式下启用AUTOPOSITIONMODE的重要性以防止类似问题。

一个线上数据丢失故障案例,引出了在 GTID 模式下 AUTO POSITION MODE 的必要性。

作者:孙绪宗

新浪微博 DBA 团队工程师,主要负责 MySQL、PostgreSQL 等关系型数据库运维。

本文来源:原创投稿

* 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

阅读前需知

  • Retrieved_Gtid_Set:从库已经接收到主库的事务编号(从库的 IO 线程已经接受到了)。
  • Executed_Gtid_Set:已经执行的事务编号(从库的执行 SQL 线程已经执行了的 SQL)。

故障现象

主从数据不一致,但是看复制是正常状态(双 Yes)。此时主库执行,从库本该报错 1062 或者 1032 的 SQL,从库复制线程还是双 Yes,没有报错。

MySQL 版本:5.7.35

故障复现

查看主库状态

MySQL [xuzong]> show master status;
+------------------+----------+--------------+------------------+-----------------------------------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                             |
+------------------+----------+--------------+------------------+-----------------------------------------------+
| mysql-bin.000008 | 39349641 |              |                  | c233aec0-58d3-11ec-a74a-a0a33ba8b3ff:1-104345 |
+------------------+----------+--------------+------------------+-----------------------------------------------+

从库上只需要设置 GTID 值的 gno 大于主库的值即可复现。

mysql> stop slave;
Query OK, 0 rows affected (0.00 sec)

mysql> reset slave all;
Query OK, 0 rows affected (0.01 sec)

mysql> reset master;
Query OK, 0 rows affected (0.01 sec)

mysql> set @@GLOBAL.GTID_PURGED='c233aec0-58d3-11ec-a74a-a0a33ba8b3ff:1-1000000'; #1000000>104345
Query OK, 0 rows affected (0.00 sec)

然后用 POSITION MODE 点位复制,启动从库,执行 show slave status \G 就会看到如下:

 Slave_IO_Running: Yes
 Slave_SQL_Running: Yes 
 ......
 Last_IO_Error_Timestamp: 
 Last_SQL_Error_Timestamp: 
           Master_SSL_Crl: 
       Master_SSL_Crlpath: 
       Retrieved_Gtid_Set: c233aec0-58d3-11ec-a74a-a0a33ba8b3ff:104221-104345
        Executed_Gtid_Set: c233aec0-58d3-11ec-a74a-a0a33ba8b3ff:1-1000000
            Auto_Position: 0
     Replicate_Rewrite_DB: 

双 Yes,看上去一点问题都没有,但是这时候在主库执行任何操作,都不会被复制,因为复制时校验 GTID 时从库表示已经执行过了。

主库

MySQL [xuzong]> insert into test(passtext) values('test');
Query OK, 1 row affected (0.00 sec)

从库

mysql> select * from xuzong.test;
+----+----------------------------------+
| id | passtext                         |
+----+----------------------------------+
|  1 | 7e5a44af63552be3f2f819cebbe0832a |
|  2 | 7e5a44af63552be3f2f819cebbe0832a |
|  3 | 7e5a44af63552be3f2f819cebbe0832a |
|  4 | d13baf7019431e0c75dee85bc923a91b |
|  5 | 11                               |
+----+----------------------------------+
5 rows in set (0.00 sec)

# 从库能看出来 Retrieved_Gtid_Set 在变化
mysql> show slave status \G
           Retrieved_Gtid_Set: c233aec0-58d3-11ec-a74a-a0a33ba8b3ff:104221-104367
            Executed_Gtid_Set: c233aec0-58d3-11ec-a74a-a0a33ba8b3ff:1-1000000

问题在于,这种不应该报错嘛,从库的 GTID 大于主库的 GTID ???

猜测

根源在于没有设置 Auto_Position=1,那么接下来验证一下猜测。

                Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Slave has more GTIDs than the master has, using the master's SERVER_UUID. This may indicate that the end of the binary log was truncated or that the last binary log file was lost, e.g., after a power or disk failure when sync_binlog != 1. The master may or may not have rolled back transactions that were already replicated to the slave. Suggest to replicate any transactions that master has rolled back from slave to master, and/or commit empty transactions on master to account for transactions that have been'
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1362229455
                  Master_UUID: c233aec0-58d3-11ec-a74a-a0a33ba8b3ff
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 230817 11:11:44
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: c233aec0-58d3-11ec-a74a-a0a33ba8b3ff:1-1000000
                Auto_Position: 1

果不其然报错了,验证了猜测。

问题处理

这种情况目前看只能重做从库。

因为案例中复制进程为双 Yes,无法被监控捕获,同时无法得知数据不一致出现的时间点,所以无法通过分析 binlog 恢复。

本案例中的实例还进行过主从切换,只能联系业务方做一次全量的数据对比,得到一个完整数据的实例,然后重新构建从库。

总结

最后总结一下 Auto_Position 的作用。

  1. 会立即清理原来的 Relay Log。
  2. 根据从库的 Executed_Gtid_SetRetrieved_Gtid_Set 的并集定位 Binlog。MASTER_LOG_FILEMASTER_LOG_POS 不存实例的值。

生产上实例开了 GTID 的话,主从复制建议用 AUTO_POSITION MODE 的方式,即 Auto_Position=1。避免一些未知操作导致从库复制线程没有预期的报错,进而导致从库未正确复制,数据丢失。

拓展:源码解读

rel_slave.cc
    --> request_dump

部分源码:
.....
  enum_server_command command= mi->is_auto_position() ?
    COM_BINLOG_DUMP_GTID : COM_BINLOG_DUMP;
......
  if (command == COM_BINLOG_DUMP_GTID)
  {
    if (gtid_executed.add_gtid_set(mi->rli->get_gtid_set()) != RETURN_STATUS_OK || // 加Retrieved_Gtid_Set
        gtid_executed.add_gtid_set(gtid_state->get_executed_gtids()) !=
        RETURN_STATUS_OK) // 加Executed_Gtid_Set
......
    int2store(ptr_buffer, binlog_flags);
    ptr_buffer+= ::BINLOG_FLAGS_INFO_SIZE;
    int4store(ptr_buffer, server_id);
    ptr_buffer+= ::BINLOG_SERVER_ID_INFO_SIZE;
    int4store(ptr_buffer, static_cast<uint32>(BINLOG_NAME_INFO_SIZE));
    ptr_buffer+= ::BINLOG_NAME_SIZE_INFO_SIZE;
    memset(ptr_buffer, 0, BINLOG_NAME_INFO_SIZE); // 设置MASTER_LOG_FILE为0
    ptr_buffer+= BINLOG_NAME_INFO_SIZE;
    int8store(ptr_buffer, 4LL); // 设置MASTER_LOG_POS为4
    ptr_buffer+= ::BINLOG_POS_INFO_SIZE; // 存gtid_set

如果 relay_log_recovery 是打开的,则忽略 Retrieved_Gtid_Set 值。

    /*
      In the init_gtid_set below we pass the mi->transaction_parser.
      This will be useful to ensure that we only add a GTID to
      the Retrieved_Gtid_Set for fully retrieved transactions. Also, it will
      be useful to ensure the Retrieved_Gtid_Set behavior when auto
      positioning is disabled (we could have transactions spanning multiple
      relay log files in this case).
      We will skip this initialization if relay_log_recovery is set in order
      to save time, as neither the GTIDs nor the transaction_parser state
      would be useful when the relay log will be cleaned up later when calling
      init_recovery.
    */ // 注释解释的很清楚,relay_log_recovery=1 会跳过初始化 gtid_retrieved_initialized
    if (!is_relay_log_recovery &&
        !gtid_retrieved_initialized &&
        relay_log.init_gtid_sets(&gtid_set, NULL,
                                 opt_slave_sql_verify_checksum,
                                 true/*true=need lock*/,
                                 &mi->transaction_parser, &gtid_partial_trx))
    {
      sql_print_error("Failed in init_gtid_sets() called from Relay_log_info::rli_init_info().");
      DBUG_RETURN(1);
    }
    gtid_retrieved_initialized= true;
#ifndef NDEBUG
    global_sid_lock->wrlock();
    gtid_set.dbug_print("set of GTIDs in relay log after initialization");
    global_sid_lock->unlock();
#endif

更多技术文章,请访问:https://opensource.actionsky.com/

关于 SQLE

爱可生开源社区的 SQLE 是一款面向数据库使用者和管理者,支持多场景审核,支持标准化上线流程,原生支持 MySQL 审核且数据库类型可扩展的 SQL 审核工具。

SQLE 获取

类型地址
版本库https://github.com/actiontech/sqle
文档https://actiontech.github.io/sqle-docs/
发布信息https://github.com/actiontech/sqle/releases
数据审核插件开发文档https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse
<think>嗯,用户问的是主从复制出现数据一致如何修复。首先,我需要回忆一下主从复制的常见问题以及解决方法。主从复制在MySQLRedis等数据库中都很常见,但这里可能主要是指MySQL的情况。数据一致的原因可能有很多,比如网络中断导致部分同步失败,主库的写操作没有正确传到从库,或者从库上有意外的写操作等等。首先,用户可能需要确认数据一致的具体情况。比如,检查主从同步状态,看看Slave_IO_Running和Slave_SQL_Running是否为Yes,或者是否有报错信息。然后,可能需要停止同步,进行数据对比,找出差异的位置。接着,考虑如何修复,比如重新同步,或者使用工具来修补数据。然后,具体的修复步骤可能包括停止从库的复制线程,备份数据,然后重新设置同步点,或者使用像pt-table-sync这样的工具来修复差异。另外,可能需要重建从库,通过主库的备份来重新做同步。这时候,需要详细说明每个步骤的操作,比如如何获取主库的binlog位置,如何导出数据并导入到从库,如何重新开始同步。 另外,还要考虑如何预防这种情况再次发生,比如监控复制延迟,避免在从库进行写操作,定期检查数据一致性等。用户可能需要了解相关的工具,比如Percona Toolkit里的pt-table-checksum和pt-table-sync,这些工具可以帮助自动检测和修复一致的问题。在给出代码示例时,可能需要用shell脚本来展示如何停止复制、获取主库状态、导出数据、导入到从库,并重新设置复制位置。同时,要解释每个步骤的作用,确保用户理解这些命令的意义和潜在风险,比如在操作前备份数据的重要性。 最后,用户可能还会关心其他相关问题,比如如何监控主从同步状态,或者如何处理主从复制中的延迟问题,这时候可以提出相关的问题供他们进一步探索。</think>主从复制数据一致的修复方案及代码示例: 1. 校验数据一致性(以MySQL为例): ```sql -- 在主库执行 SHOW MASTER STATUS; -- 记录File和Position值 -- 在从库执行 STOP SLAVE; SHOW SLAVE STATUS\G -- 检查Master_Log_File和Read_Master_Log_Pos是否匹配 ``` 2. 使用pt-table-sync工具修复(Percona Toolkit): ```bash # 安装工具 sudo apt-get install percona-toolkit # 执行数据修复(示例) pt-table-sync --execute \ --user=root --password=your_password \ h=master_host,D=test,t=users \ h=slave_host ``` 3. 重建从库方案: ```bash # 主库备份 mysqldump --all-databases --master-data=1 > full_backup.sql # 从库恢复 mysql -u root -p < full_backup.sql # 重新配置复制 CHANGE MASTER TO MASTER_HOST='master_ip', MASTER_USER='repl_user', MASTER_PASSWORD='repl_password', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=107; ``` 修复原理说明: 1. 首先需要确认复制状态和错误位置,通过`SHOW SLAVE STATUS`查看错误信息 2. pt-table-sync通过比较主从数据差异,生成修复SQL进行数据同步 3. 重建方案通过全量备份+binlog位置重置实现完全同步 4. 所有操作前必须备份数据,避免二次数据丢失 预防措施: - 定期执行`pt-table-checksum`检查数据一致性 - 设置read_only=1防止从库写入 - 监控Seconds_Behind_Master指标
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值