复制的故障与解决

由 数据损坏或丢失 引起的错误

很多故障都会促使你不得不从某个时间点开始,重新复制。

如果非正常关机使复制产生了问题,多数是由于服务器没有来得及把数据刷新到磁盘中。

主服务器意外关机

问题

如果没有配置 sync_binlog,崩溃前很有可能没来得及把几个事件刷新到磁盘中。上的 I/O 线程在崩溃发生时,可能正在读取内存中的事件,而这个事件却没有被保存到磁盘中。重启后,会重新连接,并试图再次读取该事件,但 会告诉它我这儿没有这个偏移量。

解决

解决该问题的方法是让在下一个二进制日志开头的地方开始读取。

但是,毕竟有些日志事件彻底丢失了,需要用 pt-table-checksum 检查服务器的一致性,以便修复。

事实上,这种数据丢失完全可以通过在上配置 sync_binlog 来避免的。

当然,即使配置了 sync_binlog,崩溃后,MyISAM 的数据依然可能会遭到破坏。如果 InnoDB 没有启用 innodb_flush_logs_at_trx_commit,其事务也可能会丢失(数据不会破坏)。

从服务器意外关机

问题

从服务器在意外关机之后,再次重启时,它会读取其 master.info 来分析自己上回复制到了什么位置。可是该文件没有被刷新到磁盘,因此其中的信息是错误的。可能会试图重新执行几个二进制日志事件,结果可能会产生某些唯一的索引的冲突。因为无从判断真正是在哪个位置中止的,所以没有选择,只能跳过产生的错误。

解决

可以使用 pt-slave-restart

如果使用的都是 InnoDB 表格,重启 以后,可以查看一下 MySQL 错误日志,InnoDB 的恢复过程中会输出二进制日志的坐标,一直到它恢复的时间点的位置,你可以使用这些坐标来决定把指向的什么位置。Percona Server 有一个功能,是在恢复的过程中自动提取这些信息,然后自动更新 master.info。MySQL 5.5 也可以把 master.info 和其它文件强制刷新到磁盘。

除了非正常关闭 MySQL 导致的数据丢失以外,磁盘中的二进制日志和中继日志通常不会被破坏。以下是一些更常遇到的问题:

主中的二进制日志被破坏

如果中的二进制日志被破坏,只能跳过被破坏的那部分。

可以运行 FLUSH LOGS,让其开始启用一个新的日志文件,然后把指向这个新日志的开头。

有时可以使用 SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1 来跳过某一个被破坏的事件,如果破坏的事件不止一个,只需重复这个步骤,直到所有的都被跳过。如果有很多事件被破坏,就没办法这么做了。

被破坏的事件头部会导致服务器无法查找下一个事件,此时只能做一些手工的操作来查找。

从中的中继日志被破坏

中的中继日志被破坏,如果 的二进制日志是完整的,可以使用 CHANGE MASTER TO 忽略损坏的中继日志,重新获取新的。只需把指向其当前的位置,即 Relay_Master_Log_File/Exec_Master_Log_Pos 。这会促使它抛弃磁盘中的任何中继日志。MySQL 5.5 在这方面有些改进:它可以在崩溃之后自动重新获取日志。

二进制日志与 InnoDB 事务日志 失去同步

如果崩溃了,即使某个事务没有被写入二进制日志,InnoDB 可能也会记录一个事务为已提交。没有办法来恢复丢失的事务,除非它存在于的中继日志中。MySQL 5.0 中,可以用 sync_binlog 来避免。如果是 MySQL 4.1,则使用 sync_binlogsafe_binlog 参数。

当一个二进制日志发生损坏时,能恢复多少数据取决于损坏的类型,通常有以下几种:

字节发生变化,但事件仍是有效的语句

很不幸,MySQL 竟然无法检测到这种损坏。因此一定要定期检查所有的,上面的数据是否正确。

字节发生变化,事件是无效的语句

可以用 mysqlbinlog 把这些事件提取出来,以查看有哪些混乱的数据,例如:

UPDATE tbl SET col?????????????????

尝试找到下一事件的开头,可以通过把偏移量和长度相加得到,记录下来,也许就可以跳过该事件了。

字节消失,事件长度不正确

这种情况下,执行 mysqlbinlog 有时会退出并报错,或崩溃,因为无法读取这个事件,也无法找到下一事件的开头。

几个事件被损坏,或被覆盖,或偏移量发生错位,下一事件起始于错误的偏移量

mysqlbinlog 也派不上用场。

如果损坏很严重,导致 mysqlbinlog 无法读取日志事件,只能进行一些十六进制的编辑,或使用其它技术来查找日志事件的边界。通常并不难完成,因为比较容易找到分割事件的可识别的字符。

范例:

先用 mysqlbinlog 查看一下该日志的事件偏移量:

$ mysqlbinlog mysql-bin.000113 | egrep '^# at '
# at 4
# at 98
# at 185
# at 277
# at 369
# at 447

要想在日志中查找偏移量,简单的办法是用偏移量和 strings 命令的输出进行比较:

$ strings -n 2 -t d mysql-bin.000113
	  1 binpC'G
	 25	5.0.38-Ubuntu_0ubuntu1.1-log
	 99	C'G
	146 std
	156 test
	161 create table test(a int)
	186 C'G
	233 std
	243 test
	248 insert into test(a) values(1)
	278 C'G
	325 std
	335 test
	340 insert into test(a) values(2)
	370 C'G
	417 std
	427 test
	432 drop table test
	448 D'G
	474 mysql-bin.000114

输出的结果很有辨识性,比较容易从中找到事件的开头。注意那些以 'G 结尾的字符串,它们都位于日志事件开头后面一个字节的位置,它们是固定长度的日志事件头部的部分。

在对你自己的二进制日志仔细研究一番之后,一定能找到其固定的模式,从而判断出下一个完整的日志事件偏移量。

然后,可以尝试跳过这些故障事件,可以使用 mysqlbinlog --start-position,或 CHANGE MASTER TO ... MASTER_LOG_POS

…… to be continued