大话1+1>2的sql_slave_skip_counter

(一)、开始之前的一些大补贴【附注本测试是在我的测试机器上面,主从同步性良好,无任何延迟】

a.relay-log的写入以及保留

I/Othread 会把从Matser上拉取的binlog写入到slave的relay-log里面

sql_thread每次执行同步前会把执行过的relay-log删掉


b.我们在从库上show slave status;时会看到这么一些参数:

Relay_Log_File: relay-log.000013
Relay_Log_Pos: 255

大家都知道,从库是通过sql_thread执行relay-log来进行同步的,上面这两个参数我们一般会这么理解,这是从库同步的位置,那么问题来了?如果我是stop slave;show slave status;这样的顺序来看的这个参数的,那么我重新start slave;255是sql_thread要读的是下一个位置呢?还是sql_thread已经在我stop slave前已经读到255了,现在start slave从255开始读呢?

答案是:255是sql_thread 要读的下一个位置:我怎么知道的?因为我在stop slave的时候截取了那个时候的relay-log.000013啊,那个时候的relay-log.000013长这样:

/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!40019 SET @@session.max_insert_delayed_threads=0*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#151218 8:42:23 server id 2 end_log_pos 107 Start: binlog v 4, server v 5.5.24-tmysql-1.4-log created 151218 8:42:23
BINLOG '
78ZzVg8CAAAAZwAAAGsAAAAAAAQANS41LjI0LXRteXNxbC0xLjQtbG9nAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAEzgNAAgAEgAEBAQEEgAAVAAEGggAAAAICAgCAA==
'/*!*/;
# at 107
#700101 0:00:00 server id 1 end_log_pos 0 Rotate to binlog20000.000003 pos: 5351
# at 152
#151111 7:39:22 server id 1 end_log_pos 0 Start: binlog v 4, server v 5.5.24-tmysql-1.4-log created 151111 7:39:22
BINLOG '
qvBCVg8BAAAAZwAAAAAAAAAAAAQANS41LjI0LXRteXNxbC0xLjQtbG9nAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAEzgNAAgAEgAEBAQEEgAAVAAEGggAAAAICAgCAA==
'/*!*/;
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

可以在我stop slave的时候因为I/O thread也被停掉了,看到这个时候relay-log才写到152,还没写到255,那么255肯定是还没读到的位置咯。

(二)、什么是sql_slave_skip_counter

这是一个global级别的参数,一般我们在stop slave之后,通过set global sql_slave_skip_counter=N,再start slave,来恢复因为错误而中断的主从关系。简单来说,这是mysql主从同步时,从库上遇到错误偶尔会用的一个参数,这个参数会控制从库跳过主从同步中遇到的错误,但是因为可能会导致数据不一致,所以个人比较建议用好懂的slave_exec_mode,有错误针对错误跳过错误,slave_exec_mode=IDEMPOTENT只跳过有影响的sql或者row

听了个人之言,下面来看看那么我们的bible----manual是怎么定义这个参数的呢?

SET GLOBAL sql_slave_skip_counter = N
This statement skips the next N events from the master. This is useful for recovering from replication stops caused by a statement.

This statement is valid only when the slave threads are not running. Otherwise, it produces an error.

When using this statement, it is important to understand that the binary log is actually organized as a sequence of groups known as event groups. Each event group consists of a sequence of events.

For transactional tables, an event group corresponds to a transaction.

For nontransactional tables, an event group corresponds to a single SQL statement.

Note
A single transaction can contain changes to both transactional and nontransactional tables.

When you use SET GLOBAL sql_slave_skip_counter to skip events and the result is in the middle of a group, the slave continues to skip events until it reaches the end of the group. Execution then starts with the next event group.

首先明确这么几点:

1)N是事件数,那么什么是事件,通俗一点来说(你看上面的我扣出来的relay-log,相邻两个POS之间记录的那些东东叫做一个事件

2)对于事务型表一个事件组才是一个事务(这么说吧,一个事务有begin,正文(Insert。。。那些实际操作),commit,而begin和commit也是一个事件,所以呢至少要三个事件才能构成一个事务

3)对于非事务型表,一个sql一个事件

最后最最重要的是NOTE:

如果跳过了事件,还没有到达该事务的尾部(没有读到commit;)那么没关系我也会跳过这个事务的。

所以对于事务表而言:SET GLOBAL sql_slave_skip_counter = 2;这个2的效果不等于1+1的(SET GLOBAL sql_slave_skip_counter = 2和SET GLOBAL sql_slave_skip_counter = 1效果一样,都只跳过了一个事务,但是执行了两次SET GLOBAL sql_slave_skip_counter = 1;会跳过两个事务

(三)、让我们来好好解释:

场景是这样的:现在的错误是t1主键重复(因为1,2,3);

我分别设置参数为sql_slave_skip_counter=1,2,3,4,5,6

进行了测试,下面我把测试情况和解释集成在同一个relay-log里面(几次测试relay-log的内容结构都差不多)

relay-log长成下面这样:

# at 255

#151218 8:14:33 server id 1 end_log_pos 2647 Query thread_id=2859 exec_time=0 error_code=0
SET TIMESTAMP=1450426473/*!*/;
SET @@session.pseudo_thread_id=2859/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=0/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C latin1 *//*!*/;
SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=33/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 323-----------SET GLOBAL sql_slave_skip_counter = 1;生效后从这个事件开始读起,可是因为还是在同一个事务中,所以跳到这个事务结束。
#151218 8:14:33 server id 1 end_log_pos 2734 Query thread_id=2859 exec_time=0 error_code=0
use `test`/*!*/;
SET TIMESTAMP=1450426473/*!*/;
insert into t1 value (1)
/*!*/;
# at 410-----------SET GLOBAL sql_slave_skip_counter = 2;生效后从这个事件开始读起,可是因为还是在同一个事务中,所以跳到这个事务结束。
#151218 8:14:33 server id 1 end_log_pos 2761 Xid = 136
COMMIT/*!*/;

SET GLOBAL sql_slave_skip_counter = 1;跳过一个事件,还是在事务中间,结果跳到了这里

SET GLOBAL sql_slave_skip_counter = 2;跳过两个个事件,还是在事务中间,结果跳到了这里

SET GLOBAL sql_slave_skip_counter = 3;------------------------------跳过了上面那个事务,打算从下一个事件开始读起

start slave后:

Relay_Log_Pos: 437


# at 437
#151218 8:15:16 server id 1 end_log_pos 2829 Query thread_id=2859 exec_time=0 error_code=0
SET TIMESTAMP=1450426516/*!*/;
BEGIN
/*!*/;
# at 505-----------SET GLOBAL sql_slave_skip_counter = 4;上面那个事务只有三个事件,跳过那个事务,生效后从这个事件开始读起,可是因为还是在同一个事务中,所以跳到这个事务结束。
#151218 8:15:04 server id 1 end_log_pos 2916 Query thread_id=2859 exec_time=0 error_code=0
SET TIMESTAMP=1450426504/*!*/;
insert into t1 value (2)
/*!*/;
# at 592-----------SET GLOBAL sql_slave_skip_counter = 5;上面那个事务只有三个事件,跳过那个事务,生效后从这个事件开始读起,可是因为还是在同一个事务中,所以跳到这个事务结束。
#151218 8:15:06 server id 1 end_log_pos 3003 Query thread_id=2859 exec_time=0 error_code=0
SET TIMESTAMP=1450426506/*!*/;
insert into t1 value (3)
/*!*/;
# at 679-----------SET GLOBAL sql_slave_skip_counter = 6;上面那个事务只有三个事件,跳过那个事务,生效后从这个事件开始读起,可是因为还是在同一个事务中,所以跳到这个事务结束。
#151218 8:15:16 server id 1 end_log_pos 3030 Xid = 138
COMMIT/*!*/;

SET GLOBAL sql_slave_skip_counter = 4;完整跳过了一个事务,停在了第二个事务中间,结果跳到了这里

SET GLOBAL sql_slave_skip_counter = 5;完整跳过了一个事务,停在了第二个事务中间,结果跳到了这里

SET GLOBAL sql_slave_skip_counter = 6;完整跳过了一个事务,停在了第二个事务中间,结果跳到了这里

SET GLOBAL sql_slave_skip_counter = 7;-----------------------------跳过了上面两个事务,打算从下一个事件开始读起

start slave后:

Relay_Log_Pos: 706

# at 706
#151218 8:15:29 server id 1 end_log_pos 3098 Query thread_id=2859 exec_time=0 error_code=0
SET TIMESTAMP=1450426529/*!*/;
BEGIN
/*!*/;
# at 774
#151218 8:15:29 server id 1 end_log_pos 3185 Query thread_id=2859 exec_time=0 error_code=0
SET TIMESTAMP=1450426529/*!*/;
insert into t1 value (4)
/*!*/;
# at 861
#151218 8:15:29 server id 1 end_log_pos 3212 Xid = 141
COMMIT/*!*/;

(四)总结

重要的事情再说一遍:

1)set global sql_slave_skip_counter=2;不等于进行两次set global sql_slave_skip_counter=1;(前者只能跳过一个事务,后者每次设置为一都能跳过一个事务,所以两次能跳过两个事务),所以建议尽量不用使用set global sql_slave_skip_counter=N(N>1);

2)刚刚上面看到了,set global sql_slave_skip_counter=N;这是依次跳过事件的,那么对于一个事务有多个事件,即使是set global sql_slave_skip_counter=1;也有可能使得一个多事件的事务(其中有有问题事件和没问题事件)里面的有用事件被跳过造成数据不一致,所以建议用slave_exec_mode来跳过错误。


你可能感兴趣的:(mysql)