需求描述

1.内网服务器有俩套主从复制环境,一套是基于传统复制的5.6.26版本,另外一套是基于GTID的5.7.19版本的复制。现在开发的需求是需要将基于传统复制的上面的俩个表同步到基于基于GTID复制上面去,并且要求同步的俩个表中有一个表的一列的值必须是源的10倍。

root@mysqldb 15:51:  [remix_test]> show create table sbtest1 \G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL,
  `k` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

表结构如上所示,在原来的基础上column k必须乘以10,即new_k = old_k*10

难点

1.如何将俩个表的数据同步过来。在这里我们采用了使用主从同步的方案,但是主从同步的方案又带来了以下几个问题:

  • 基于传统复制的主从是根据binlog file 和 binlog position进行复制的,基于GTID的主从是根据GTID进行复制,这俩者创建主从是使用基于传统复制还是GTID复制。
  • 基于GTID复制的主从的binlog的格式是ROW格式,基于传统复制的主从的binlog格式是STATEMENT格式,在将数据同步到从传统复制的master上面同步到基于GTID复制的master上,这个时候基于GTID的主从复制的SLAVE是否能够正常应用binlog日志,或者说slave能否应用没有使用GTID的binlog日志。
  • 仅仅只是需要同步俩张表,其他的库表的数据不需要同步过来。所以需要进行复制过滤。

2.如何将同步过来的数据做修改。在这里我们采取的方案是使用触发器,但是触发器本身会消耗资源;因为触发器是基于row行的,所以假如我们一次性修改500条数据花费1s中,触发器触发修改会触发500次,加入每行数据修改花费1s,那么触发器就会花费500s。

  • 在测试的过程中,我们发现在INSERT插入一行数据之后,在针对这行的数据进行update操作,触发器无法被应用到,并且show slave status\G会报错:
    Last_SQL_Error: Error 'Can't update table 'sbtest1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.' on query. Default database: 'remix_test'. Query: 'insert into sbtest1(id,k) values(39039,1),(39040,10)'

解决方案

1.数据同步:

  • 为了使得基于GTID主从复制的master能够应用来自于传统复制的主从的master的binlog日志,在GTID主从复制的master上面必须修改GTID_MODE,将GTID_MODE更改成ON_PERMISSIVE(本地产生GTID事务,并且接受隐性事务(即非GTID事务)):
    mysql> set global gtid_mode='ON_PERMISSIVE';
    • 至于基于GTID复制的主从的SLAVE同步来自于MASTER的数据,这里暂时没有什么好的方案,或者我们还没有进行实验。但是之前我们忘记在本地进行修改GTID_MODE导致在SLAVE上面执行SHOW SLAVE STATUS \G可以看出很明显的错误:ERROR:1236 。这个问题也是我们现在遇到无法解决的问题,因为哪怕最后基于GTID复制的MASTER可以接受来自于基于传统复制的MASTER的binlog日志,但是基于GTID复制的SLAVE却无法从MASTER上面接受和应用这些日志。
      Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Cannot replicate anonymous transaction when AUTO_POSITION = 1, at file /home/mysql/data_3307/mysql-bin.000005, position 234.; the first event '' at 4, the last event read from '/home/mysql/data_3307/mysql-bin.000005' at 299, the last byte read from '/home/mysql/data_3307/mysql-bin.000005' at 299.'
  • 至于基于GTID主从复制的SLAVE不能应用来自于MASTER的日志,在这里解决方案就是将基于GTID复制转换成传统复制。就是在show slave status \G获取Master_Log_File和Read_Master_Log_Pos信息,然后stop slave,chaneg master to master_log_file=Master_Log_File,master_log_pos=Read_Master_Log_Po,master_auto_position=0;然后在start slave。还有就是最好是在低峰期的时候做一下主从数据一致性检测或者直接查看GTID的集合信息。
  • 复制过滤:需要注意的如果你仅仅只是写replication_do_table的话,虽然一般的DDL,DML操作不是这俩个表是不会复制过来的,但是当你创建新库的时候(create database schema_name;)还是会将新库的创建命令同步过来的,但也是仅仅同步新库的创建命令,其他的例如在新库创建表之类的是不会同步过来的。
    mysql> Change replication filter REPLICATE_DO_DB=(remix_test),REPLICATE_DO_TABLE = (remix_test.sbtest1,remix_test.sbtest2);
  1. 触发器的创建。
    • 创建下面触发器的时候虽然是创建成功的,但是但你在基于传统复制的master做insert插入操作的时候是可以触发触发器但是却无法执行,会抛出上面所说的错误。
      mysql> delimiter || ;
      mysql> create trigger tr1 after insert on sbtest1
      -> for each row
      -> begin
      ->     update sbtest1 set k=new.k*10 where id=new.id;
      -> end||
      mysql> delimiter ;
    • 最后还是打算创建一个新表,每次旧表又什么操作就在新表上面进行修改。

实际操作:

  1. 基于传统复制数据的slave上面的备份
    shell> mysqldump --set-gtid-purged=OFF --single-transaction --dump-slave=2 -uroot -p -t remix_test sbtest1 > sbtest1_20180402.sql
    shell> mysqldump --set-gtid-purged=OFF --single-transaction --dump-slave=2 -uroot -p -t remix_test sbtest2 > sbtest2_20180402.sql
  2. 基于GTID复制的MASTER上面的恢复
    shell> mysql -S /var/lib/mysql/mysql_3306.sock -uroot -p remix_test< sbtest1_20180402.sql
    shell> mysql -S /var/lib/mysql/mysql_3306.sock -uroot -p remix_test
  3. 基于传统复制的MASTER上面创建复制账号
    mysql> grant replication slave on 'slave'@'ip_address' identified by 'new_password';
    mysql> flush privileges;
  4. 基于GTID复制的MASTER的gtid_node修改
    mysql> set global gtid_mode='ON_PERMISSIVE';
  5. 基于GTID复制的MASTER上面搭建主从(从基于后面备份的sbtest2_20180402.sql上面获取binlog file和position信息)
    mysql> change master  ......
  6. 基于GTID复制的MASTER上面做复制过滤
    mysql> change replication filter replicate_do_db=(remix_test),replicate_do_table=(remix_test.sbtest1,remix_test.sbtest2);
  7. 基于GTID复制的MASTER开启主从复制
    【20180402】MySQL关于replication filter和trigger的一些应用_第1张图片
  8. 基于GTID复制的MASTER创建新表
    mysql> create table sbtest1_bak like sbtest1;
    mysql> insert into sbtest1_bak select id,k*10 from sbtest1;

    9.基于GTID复制的MASTER上面创建触发器

    root@mysqldb 15:39:  [remix_test]> delimiter ||
    root@mysqldb 15:41:  [remix_test]> create trigger tr2 after update on sbtest1
    -> for each row
    -> begin
    ->     update sbtest1_bak set k=new.k*10 where id=new.id;
    -> end||
    root@mysqldb 15:48:  [remix_test]> delimiter ||
    root@mysqldb 15:48:  [remix_test]> create trigger tr3 after delete on sbtest1
    -> for each row
    -> begin
    ->     delete from sbtest1_bak where id=old.id;
    -> end||
    Query OK, 0 rows affected (0.11 sec)
    root@mysqldb 15:49:  [remix_test]> delimiter ;
    root@mysqldb 15:06:  [remix_test]> create trigger tr1 after insert on sbtest1
    -> for each row
    -> begin
    ->     insert into sbtest1_bak(id,k) select id,k*10 from sbtest1 where id=new.id;
    -> end||
    Query OK, 0 rows affected (0.11 sec)
    root@mysqldb 15:08:  [remix_test]> delimiter ;

    【20180402】MySQL关于replication filter和trigger的一些应用_第2张图片

20180615 补充 5.7版本的多源复制和过滤

MySQL5.7已经开始支持多源复制了,主要语法是:

mysql> change master to ........ for channel 'channel_name';

但是我们线上在开启多源复制的时候还需要针对单个channel做过滤,这时候我们发现MySQL5.7的版本并不支持针对单个channel做过滤,需要升级到MySQL8.0以上,过滤规则是对所有的channel都会起作用。

在偶然的一次有做复制过滤的MySQL实例做了重启,在启动的slave的时候突然发现报错,show slave status的时候看到之前replication filter的规则全部丢失了。所以需要注意一下。