多年DBA工作,也遇到很多数据库疑难杂症,其处理和分析值得记录和分享,准备写一个系列文章,这是第1篇。
MySQL备机的复制全部中断是非常危险的场景,如果是io_thread异常,因为开启了半同步,直接会导致主机卡主,如果是sql_thread异常,也会导致备机延迟,主备无法自动切换,如果此时主机再故障,则业务读写都会出现异常!
收到实例A复制延迟报警,查看监控发现该实例所有备机复制都延迟了,在备机上执行
show slave status\G;
发现io_thread正常,但sql_thread异常中断,Last_SQL_Error是:
Query caused different errors on master and slave.
Error on master: message (format)='Invalid error code' error code=126;
Error on slave:actual message='no error', error code=0.
Default database:''. Query:'drop user if exists 'test'@'xx.xx.xx.xx'
解析binlog,发现报错事务对应的GTID是: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx: 12018988743
查看主机报错日志:
[ERROR] /usr/local/mysql/bin/mysqld: Incorrect key file for table './mysql/user.MYI'; try to repair it
[ERROR] /usr/local/mysql/bin/mysqld: Incorrect key file for table './mysql/user.MYI'; try to repair it
[ERROR] Got an error from thread_id=11,
/export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/storage/myisam/mi_delete.c:123
查看备机报错日志:
2024-03-28T11:42:48.384779+08:00 18280 ERROR Slave S0L for channel ':
Worker 1 failed executing transaction
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx: 12018988743' at master log binlog.000020, end_log_pos 1031556987;
Query caused different errors on
master and slave. Error on master:
message (format)='Invalid error code' error code=126;
Error on slave:actual message='no error',
error code=0. Default database:'
Query:'drop user if exists 'test'@'xx.xx.xx.xx'
查看主机binlog日志:
查看备机binlog日志:
一般遇到复制报错,在确保数据一致性的情况下,有2种方案:跳过复制出错事务,或者不记binlog手工执行语句。
STOP SLAVE;
SET GTID_NEXT='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:12018988743';
BEGIN; COMMIT;
SET GTID_NEXT='AUTOMATIC';
START SLAVE;
发现没有效果
stop slave;
SET session sql_log_bin = 0;
drop user if exists 'test'@'xx.xx.xx.xx';
start slave;
发现没有效果
这时深入分析备机复制状态,发现备机的Executed Gtid Set: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:1-12018988746
。 12018988746
大于12018988743
,表明报错的事务已经执行过并执行成功了!
那么我们是否可以重置备机复制状态,然后用GTID自动找点恢复复制?
stop slave;reset slave all;
change master to master_host='xx.xx.xx.xx', master_port=xxxx, master_user='xxxx',
master_password='xxxx', master_auto_position=1;start slave;
复制恢复,尝试成功!
说明: 在 MySQL 中,RESET SLAVE [ALL]
命令用于重置复制从服务器的状态。这个命令会清除从服务器上与复制相关的所有状态和配置,使其停止从主服务器接收复制事件,并准备重新配置复制。
ALL
关键字时,该命令会删除从服务器上的中继日志(relay logs),并重置复制相关的系统变量,但不会改变复制配置(如主服务器的地址和复制的用户凭证)。ALL
关键字时,该命令不仅执行 RESET SLAVE
命令的所有操作,还会清除复制配置,包括主服务器的信息、用户凭证等。这意味着,执行 RESET SLAVE ALL
后,你需要重新配置复制关系,包括执行 CHANGE MASTER TO
语句来指定新的主服务器信息。注意虽然重置了复制状态,但是已经执行的事务的GTID信息不变,所以可以用自动找点恢复复制关系!
修复问题后,我们来深入分析下原因:
MySQL5.7中mysql.user为MyISAM引擎,实例异常crash后,触发表损坏。
MySQL5.7 binlog格式设置为row模式,但DCL语句(Data Control Language 语句是指用于控制数据库系统中数据访问和权限的SQL语句)会记录query event,下游备机回放query event,会检查relay日志和下游本地回放的err_code信息,如果不一致,复制会报错。我们来看下具体代码:
if((expected_error&& rpl_filter->db_ok(thd->db().str) &&
expected_error != actual_error &&
!concurrency_error_code(expected_error)) &&
!ignored_error_code(actual_error) &&
!ignored_error_code(expected_error))
{
if (!ignored_error_code(ER_INCONSISTENT_ERROR))
{
rli->report(ERROR_LEVEL, ER_INCONSISTENT_ERROR,
ER(ER_INCONSISTENT_ERROR),
ER_THD(thd, expected_error), expected_error,
(actual_error ?
thd->get_stmt_da()->message_text() :
"no error"),
actual_error, print_slave_db_safe(db), query_arg);
thd->is_slave_error= 1;
}
代码说明:
expected_error && rpl_filter->db_ok(thd->db().str)
:预期错误(expected_error)不为零,且当前数据库(通过线程的 db 属性获取)通过了复制过滤(rpl_filter->db_ok)检查。expected_error != actual_error
:预期的错误代码与实际发生的错误代码不相同。!concurrency_error_code(expected_error)
:预期的错误不是并发错误。!ignored_error_code(actual_error) && !ignored_error_code(expected_error)
:既实际发生的错误也预期的错误都不是被忽略的错误。
如果以上所有条件都满足,进入错误处理逻辑。ER_INCONSISTENT_ERROR
(一个特定的错误码,指示数据或状态不一致)是否不被忽略。如果不被忽略,那么:- 使用 rli->report
方法报告一个 ER_INCONSISTENT_ERROR
错误,这表明有一个不一致的错误状态发生。这里会包含一些错误信息,如预期的错误码、实际的错误信息(如果有的话)、实际的错误码、受影响的数据库名(经过安全处理),以及相关的 SQL 查询(query_arg)。thd->is_slave_error= 1;
)。3.总结:
这段代码的主要作用是在复制过程中检测错误状态的一致性。当预期发生某个错误,但实际发生的错误与之不同时(并且这些错误都不是并发错误或被忽略的错误),代码将报告一个不一致的错误(ER_INCONSISTENT_ERROR
),并将当前线程标记为遇到了错误。这个机制是数据复制中错误处理和数据一致性维护的重要部分,确保了在复制过程中遇到异常情况时,能够及时识别并报告问题。
因为主机err_code是126(Index file is crashed),而备机执行成功,error code 是0,2者不一致,所以复制报错中断!
现在还有1个问题,为什么sql_thread没有停在GTID 12018988743,而是又往前执行了3个事务,GTID停留在 12018988746 ?分析线上实例都开启了并行复制,所以怀疑和并行回放有关,我们找到报错事务的last_commited=163886,然后grep下,发现确实事务12018988743-12018988746对应的last_commited都是163886。
说明: last_committed表示事务在每个二进制日志文件中的 Binlog Group 编号,sequence number 为每个二进制日志文件中事务的编号,last committec会有重复的值,last_committed 值相同表示事务处于同一个 Binlog Group 中、也表示主库中这些事务在并行提交时没有锁冲突。从库在应用二进制日志时,具有相同ast_committed值的事务可以并行回放,在每个二进制日志文件中,sequence_number的值不允许重复。
升级到MySQL8.0,系统表全部换成事务型的innodb表。若系统表写入失败,不会记录binlog,也就不会导致上面的复制中断问题。
微信公众号"数据库之巅"记录了我在互联网金融数据库运维中走过的路和踩过的坑,感兴趣的同学可以关注。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。