• MySQL复制 slave_exec_mode 参数IDEMPOTENT 说明


     

     

    背景:

          今天无意当中看到参数slave_exec_mode,从手册里的说明看出该参数和MySQL复制相关,是可以动态修改的变量,默认是STRICT模式(严格模式),可选值有IDEMPOTENT模式(幂等模式)。设置成IDEMPOTENT模式可以让从库避免1032(从库上不存在的键)和1062(重复键,需要存在主键或则唯一键)的错误,该模式只有在ROW EVENT的binlog模式下生效,在STATEMENT EVENT的binlog模式下无效。IDEMPOTENT模式主要用于多主复制和NDB CLUSTER的情况下,其他情况不建议使用。从上面的介绍来看,这个参数的让从库跳过指定的错误,那问题来了:

    1:和 sql_slave_skip_counter 比,有什么好处?

    2:和 slave-skip-errors = N比,有什么好处?

    带着这2个问题,本文来进行相关的测试和说明。 

    环境:

    MySQL版本:Percona MySQL 5.7

    复制模式:ROW,没有开启GTID

    测试:

    ① 1062 错误:Could not execute ... event on table db.x; Duplicate entry 'xx' for key 'PRIMARY', Error_code: 1062;

    主从上的测试表结构:

    CREATE TABLE `x` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

    主从上的表记录:

    M:

     
    select * from x;
    +----+
    | id |
    +----+
    |  2 |
    |  3 |
    +----+
    2 rows in set (0.01 sec)
     

    S:

     
    select * from x;
    +----+
    | id |
    +----+
    |  1 |
    |  2 |
    |  3 |
    +----+
    3 rows in set (0.00 sec)
     

    主从上的表记录本来就不一致了,主上缺少了id=1的记录。

    此时从上的slave_exec_mode为默认的STRICT模式:

     
    show variables like 'slave_exec_mode';
    +-----------------+--------+
    | Variable_name   | Value  |
    +-----------------+--------+
    | slave_exec_mode | STRICT |
    +-----------------+--------+
    1 row in set (0.00 sec) 
     

    M上的binlog模式为:

    show variables like 'binlog_format';                                                                                                            +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | binlog_format | ROW   |
    +---------------+-------+
    1 row in set (0.00 sec)

    在M上执行:

    insert into x values(1),(4),(5);
    Query OK, 3 rows affected (0.00 sec)
    Records: 3  Duplicates: 0  Warnings: 0

    因为从上已经存在了id=1的记录,此时从的复制就报了1062的错误:

    Last_SQL_Errno: 1062
    Last_SQL_Error: Could not execute Write_rows event on table dba_test.x; Duplicate entry '1' for key 'PRIMARY', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log mysql-bin-3306.000006, end_log_pos 7124

    出现这个错误时,大家的一致做法就是执行:sql_slave_skip_counter=N。关于该参数的说明可以看MySQL小误区:关于set global sql_slave_skip_counter=N 命令的一些点。文章的总结是:

     
      1、set global sql_slave_skip_counter=N中的N是指跳过N个event
    
      2、最好记的是N被设置为1时,效果跳过下一个事务。
    
      3、跳过第N个event后,位置若刚好落在一个事务内部,则会跳过这整个事务
    
      4、一个insert/update/delete不一定只对应一个event,由引擎和日志格式决定
     

    sql_slave_skip_counter的单位是“event”,很多人认为该参数的单位是“事务”,其实是错误的,因为一个事务里包含了多个event,跳过N个可能还是在同一个事务当中。对于上面出现1062的错误,把N设置成1~4效果是一样的,都是跳过一个事务。因为执行的SQL生成了4个event:

     
    show binlog events in 'mysql-bin-3306.000006' from 6950;
    +-----------------------+------+------------+-----------+-------------+---------------------------------+
    | Log_name              | Pos  | Event_type | Server_id | End_log_pos | Info                            |
    +-----------------------+------+------------+-----------+-------------+---------------------------------+
    | mysql-bin-3306.000006 | 6950 | Query      |       169 |        7026 | BEGIN                           |
    | mysql-bin-3306.000006 | 7026 | Table_map  |       169 |        7074 | table_id: 707 (dba_test.x)      |
    | mysql-bin-3306.000006 | 7074 | Write_rows |       169 |        7124 | table_id: 707 flags: STMT_END_F |
    | mysql-bin-3306.000006 | 7124 | Xid        |       169 |        7155 | COMMIT /* xid=74803 */          |
    +-----------------------+------+------------+-----------+-------------+---------------------------------+
    4 rows in set (0.00 sec)
     

    所以处理该错误的方法有:

    1:skip_slavesql_slave_skip_counter

     
    stop slave;                                                                                                                                     Query OK, 0 rows affected (0.00 sec)
    
    set global sql_slave_skip_counter=[1-4];
    Query OK, 0 rows affected (0.00 sec)
    
    start slave;
    Query OK, 0 rows affected (0.00 sec)
     

    2:在配置文件里指定slave-skip-errors=1062(需要重启)

    这2种方法都能让复制恢复正常,但是会让主从数据不一致(谨慎使用),让从库丢失了id=4和5的记录。并且第2种方法还需要重启数据库,这时本文介绍的slave_exec_mode参数就派上用场了。在从库上设置该参数

     
    set global slave_exec_mode='IDEMPOTENT';
    Query OK, 0 rows affected (0.00 sec)
    
    stop slave;                                                                                                                                     Query OK, 0 rows affected (0.00 sec)
    
    start slave;
    Query OK, 0 rows affected (0.00 sec)
     

    同样在主上执行:

    insert into x values(1),(4),(5);

    可以惊喜的发现主从数据是同步的,没有出现复制异常:

     
    M:
    select * from x;                                                                                                                                +----+
    | id |
    +----+
    |  1 |
    |  2 |
    |  3 |
    |  4 |
    |  5 |
    +----+
    5 rows in set (0.00 sec)
    
    S:
    select * from x;                                                                                                                                +----+
    | id |
    +----+
    |  1 |
    |  2 |
    |  3 |
    |  4 |
    |  5 |
    +----+
    5 rows in set (0.01 sec)
     

    上面的测试可以看到,参数设置成slave_exec_mode='IDEMPOTENT' 后,可以跳过出一个错误的event。

    ② 1032错误:Could not execute ... event on table db.x; Can't find record in 'x', Error_code: 1032;

    这个错误的出现是因为ROW模式下的复制,对数据的一致性有了很严的要求,具体的可以看MySQL Binlog 【ROW】和【STATEMENT】选择

    主从上的测试表结构:

    CREATE TABLE `x` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

    主从上的表记录:

    M:

     
    select * from x;                                                                                                                                +----+
    | id |
    +----+
    |  1 |
    |  2 |
    |  3 |
    +----+
    3 rows in set (0.00 sec)
     

    S:

     
    select * from x;
    +----+
    | id |
    +----+
    |  1 |
    |  3 |
    +----+
    2 rows in set (0.00 sec)
     

    主从上的表记录本来就不一致了,从上缺少了id=2的记录。此时从上的slave_exec_mode为默认的STRICT模式:

     
    show variables like 'slave_exec_mode';
    +-----------------+--------+
    | Variable_name   | Value  |
    +-----------------+--------+
    | slave_exec_mode | STRICT |
    +-----------------+--------+
    1 row in set (0.00 sec) 
     

    M上的binlog模式为:

    show variables like 'binlog_format';                                                                                                            +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | binlog_format | ROW   |
    +---------------+-------+
    1 row in set (0.00 sec)

    在M上执行:

    BEGIN;
    INSERT INTO x SELECT 4;
    DELETE FROM x WHERE id = 2;
    INSERT INTO x SELECT 5;
    COMMIT;

    因为从上不存在了id=2的记录,此时从的复制就报了1032的错误:

    Last_SQL_Errno: 1032
    Last_SQL_Error: Could not execute Delete_rows event on table dba_test.x; Can't find record in 'x', Error_code: 1032; handler error HA_ERR_KEY_NOT_FOUND; the event's master log mysql-bin-3306.000006, end_log_pos 12102

    同样的,在上面测试中说明的2种方法可以让复制正常,但是数据也一样会丢失。丢失了id=4和5的记录,继续在从库上设置该参数:

     
    set global slave_exec_mode='IDEMPOTENT';
    Query OK, 0 rows affected (0.00 sec)
    
    stop slave;                                                                                                                                     Query OK, 0 rows affected (0.00 sec)
    
    start slave;
    Query OK, 0 rows affected (0.00 sec)
     

    在M上执行同样的操作:

    BEGIN;
    INSERT INTO x SELECT 4;
    DELETE FROM x WHERE id = 2;
    INSERT INTO x SELECT 5;
    COMMIT;

    也可以惊喜的发现主从数据是同步的,没有出现复制异常。

    注意:slave_exec_mode='IDEMPOTENT'不能对DDL操作幂等,并且也不能对字段长度不同导致的错误进行幂等,如把例子中的从库表的id字段类型int改成bigint。并且只能在binlog_format为ROW的模式下使用,而且只能对1032和1062进行幂等模式。

    总结:

          对于上面的测试总结针对slave_exec_mode参数,它可以跳过1062和1032的错误,并且不影响同一个事务中正常的数据执行。如果是多个SQL组成的事务,则可以跳过有问题的event

          看着这个参数很不错,但手册上说明不建议在普通的复制环境中开启。对于NDB以外的存储引擎,只有在确定可以安全地忽略重复键错误和没有键的错误时,才应使用IDEMPOTENT模式。这参数是专门针对NBD Cluster进行设计的,NBD Cluster模式下,该参数只能设置成IDEMPOTENT模式。所以要根据自己的应用场景来决定,正常情况下,主从是一致的,有任何错误发生都要报错,不过在做特殊处理时,可以临时开启。

          另外在GTID模式下的复制,sql_slave_skip_counter是不支持的 -

    root@localhost:3306.sock [(none)]>set sql_slave_skip_counter=1;
    ERROR 1229 (HY000): Variable 'sql_slave_skip_counter' is a GLOBAL variable and should be set with SET GLOBAL
    root@localhost:3306.sock [(none)]>set global sql_slave_skip_counter=1;
    ERROR 1858 (HY000): sql_slave_skip_counter can not be set when the server is running with @@GLOBAL.GTID_MODE = ON. Instead, for each transaction that you want to skip, generate an empty transaction with the same GTID as the transaction

    slave_exec_mode=IDEMPOTENT 在MySQL复制环境中是个很有用的参数:只要在备机运行set global slave_exec_mode=IDEMPOTENT ,备机的sql thread就运行在冥等模式下,可以让备机在insert主键、唯一键冲突,update、delete值未找到错误发生时不断开复制而保持冥等性(当即生效,连slave的sql线程都不用重启哟);而类似sql_slave_skip_counter=Nslave-skip-errors = N 这样的粗暴跳过错误方法可能破坏主备一致性。但官方文档的描述很简洁,我一直好奇slave_exec_mode=IDEMPOTENT 是如何在复制出错时保持一致性的--譬如主键冲突时是简单跳过还是覆写,今天在Percona 5.7下做了个实验(binlog是row格式),实验过程就省略了,直接总结如下:

    1.insert场景
    此时insert into语句在备机的效果就跟replace into一样,但却并不是把insert into转换成replace into来执行,分两种情况:
    a.MySQL配置成autocommit,直接一条insert into ...
    如这样的insert
    insert into test set c1='a',c2='b';

    此时insert into语句在备机执行时假如遇到主键冲突就先转化为delete再insert

    delete from test where c1='old_value' and c2='old_value';   
    insert into test set c1='a',c2='b';   
    

    假如遇到非主键的唯一键冲突就转换为update
    update test set set c1='a',c2='b' where c1='old_value' and c2='old_value';

    b.当显示开始事务时(begin...insert into...commit;)
    如这样的sql

    begin;   
    ...... 
    insert into test set c1='a',c2='b';   
    ...... 
    commit;   
    

    此时begin...commit里的insert into语句在备机执行时假如遇到主键冲突、唯一键冲突都是先转化为delete再insert

    begin;   
    ...... 
    delete from test where c1='old_value' and c2='old_value';   
    insert into test set c1='a',c2='b';   
    ...... 
    commit;   
    

    2.update场景
    当备机不存在要更新的记录,这条update跳过不执行

    3.delete场景
    同update场景一样,备机跳过此delete啥也不干

    注意:使用冥等模式时表要有主键
    冥等模式并不是万能的,除了不能对DDL操作冥等,对字段长度不同导致的错误也不是冥等(譬如主机一个字段是char(20)而备机是char(10)),还有一个限制就是表有主键才会对insert的冥等设置有效:因为insert的冥等行为是通过主键来判断备机是否有重复值从而产生覆写操作,如果表没有主键,则备机即使设了冥等也可能会比主机多重复数据。




  • 相关阅读:
    爱情戒指
    李小龙
    20分钟
    大话JAVA(二)
    编程高手
    Free Computer Books, Free eBooks and Read Free Books Online
    (06) [修正版] 判断整数序列是不是二元查找树的后序遍历结果
    [原创]DateTime在使用 format Custom Date and Time Format Strings时遇到的问题和解决方法
    [原创]00:矩形算法题二分法的扩展(2分法 * 2分法)
    用堆栈和用递归分别实现倒序打印
  • 原文地址:https://www.cnblogs.com/DataArt/p/10229657.html
Copyright © 2020-2023  润新知