天天看点

数据库内核月报 - 2015 / 06-MySQL · 答疑解惑 · binlog event 中的 error code

rds 有个任务叫做恢复到任意时间点,相当于一个数据时光机,可以将数据恢复到过去任意一个时间点,在用户出现误操作需要将数据找回时非常有用。这个功能主要是通过备份集恢复 + binlog回放实现,在用备份集恢复出的实例上应用 binlog 到指定时间点。

然而最近线上重放binlog时遇到了这样一个错误:

查看对应 binlog 的,发现这是一个 create view 语句,而备份集恢复出来的实例上确实已经有了这个view,再往前翻看binlog,并没有发现 drop 这个 view 的记录,倒是找到了create 这个 view 的记录,仔细比较2处 create view 的binlog event,会发现后者多了个 error_code=1050,这个是什么错呢:

1050 对应的错就是 table already exists

就是说 create view 失败了,仍然记入 binlog 了,但是当时备库并没有这个错误中断掉。

复现非常简单,连着执行同一个create view语句即可。

查看binlog event

可以清楚的看到,第二次 create view 时error_code 为 1050。

查看 binlog 对应的代码,发现 error_code 这个字段是 <code>query_log_event</code> 的专属,其它的如 row_event、gtid event等都没有这个字段。而备库在执行<code>query_log_event</code> 时会检查event 的 error_code(存入expected_error),如果非0的话,就和当前sql线程执行出错(存入actual_error)比较,看是否一致,如果一致的话就算执行成功,如果不一致的话,就再检查这个错是否能够忽略,如配置了 slave_skip_errors,代码片段如下(在<code>query_log_event::do_apply_event</code>中):

正常的想法应该是执行出错,就不应该记binlog,为什么会有这样的设计呢,主库错,记binlog,然后备库要求同样的错。

因为ddl是不能回滚的,如果ddl执行到一半报错,主库又不能回滚,那么应该如何通知备库它做了一半呢?就是把错记下去,期待备库也报同样的错。

在此之前,备库对于 <code>query_log_event</code> 执行出错是这样处理的,先检查sql线程执行出错是不是因为表不存在,如果是的话,就单独再开个连接,从主库把不存在的表导过来(<code>fetch_nx_table</code>),然后再重试执行失败的event,如果还有不存在的表,就再拉,再重复执行;对于其它的错就直接报错。

现在看起来是不是很奇葩,2000年的时候,mysql还是很年青的哇 =_=

我们在回放binlog的时候用的是mysql client,不是sql线程,mysql client中并没有对error_cocd的处理逻辑,因此遇到执行出错就直接报错了。

所以如果脚本或者代码里有这种重放binlog逻辑的,需要注意处理这种场景。