在早期的内核月报,有一篇 ,介绍阿里在 5.5 版本中自己实现并行复制时遇到的外键约束问题,本文接着前作继续介绍并行复制外键约束问题,这次场景不一样,并且目前官方 5.6 最新版本(5.6.30)中也有这个问题。

    一般情况的复制是 A->B 这样一主一备,本文要描述的场景是 A->B->C 这样一主两备,并且备库级联,其中备库 C 开启了并行复制,B 可以串行也可以并行,binlog_fomat 都是 row。

    在主库A上执行如下语句:

    如前文并行复制外建约束问题 所述,5.6 并行复制已经解了外键问题,遇到被外键约束的表,会先切为串行,当前事务执行完成后,再开始并行,为什么还会出问题呢?分析这个问题前,我们先来看下,5.6 是怎么解决外键约束问题的。

    5.6 并行复制是基于db进行分发的,不同的db分发到不同的 worker 线程,对 row 格式的 binlog,分发信息是体现在 table_map event 中的。5.6 对 table_map 中加了一个专门的 flag ,表示当前表被外键约束(具体参考commit ),这样备库分发线程(Coordinator)在遇到有这种标志的 table_map,就切换为串行,具体逻辑参考Log_event::get_slave_worker()apply_event_and_update_pos()

    这个机制是没问题的,如果 flag 能从 A 传到 B 再传到 C,就不会出现这个问题,现在问题的出现是因为备库 B 执行完父表(parent)的更新后,写 binlog 时 flag 没写进去,导致 C 在并行模式下执行 parent 表更新时,没有切换到串行模式,和 child 表的更新同时在跑,如果执行 child 表更新的 worker 先做,那么就会出现外键约束报错。

    如果当前访问到的 db 个数为1,并且 db 是空字符串 的话,就设置这个 flag。binlog_accessed_db_names 中只有 "" 这一个元素是一个特殊构造的场景,正常情况下db不会是 ""的,构造这样 db 的逻辑在 THD::decide_logging_format,如下:

    可以看到,如果有当前表被外键约束的话(),会清掉binlog_accessed_db_names,只放一个空字符串进去。

    但是 SQL 线程在应用 row_event 时,不会走到上面的逻辑,因为 lex->sql_command 的值为 SQLCOM_END,所以备库 B 生成的 parent 表的 table_map 就不包含这个 flag。