MySQL5.7 GTID学习笔记,[MySQL 5.6] GTID实现、运维变化及存在的bug_weixin_30481087的博客-程序员秘密

技术标签: 网络  运维  数据库  

 

 

GTID(global transaction identifier)是对于一个已提交事务的全局唯一编号,前一部分是server_uuid,后面一部分是执行事务的唯一标志,通常是自增的。

下表整理了GTID常用的查看命令,以及变量的描述及原理,供大家参考(以下基于MySQL5.7,对于MySQL5.6的GTID由于存在性能问题,不推荐使用)

 

角色

常用查看GTID的相关命令

变量示例

描述

变量的更新时机

master

show global variables like '%gtid%';

gtid_mode=ON

GTID模式,开启GTID时在配置文件中开启

gtid_mode参数的4种选项

1> GTID_MODE = OFF : 不产生Normal_GTID,只接受来自master的ANONYMOUS_GTID

2> GTID_MODE = OFF_PERMISSIVE : 不产生Normal_GTID,可以接受来自master的ANONYMOUS_GTID & Normal_GTID

3> GTID_MODE = ON_PERMISSIVE : 产生Normal_GTID,可以接受来自master的ANONYMOUS_GTID & Normal_GTID

4> GTID_MODE = ON : 产生Normal_GTID,只接受来自master的Normal_GTID

在my.cnf配置中指定,或者online可以通过下面修改

SET @@GLOBAL.GTID_MODE=

SET GLOBAL GTID_MODE=

 

 

enforce_gtid_consistency=ON

保证GTID安全的参数,开启GTID时在配置文件中开启

ENFORCE_GTID_CONSISTENCY参数的3种选项

1> OFF:所有事务允许违反gtid一致性。

2> ON:没有事务允许违反gtid一致性。

3> WARN:所有事务允许违反gtid一致性,但一个警告会产生,Mysql5.7.6新增。

在my.cnf配置中指定,或者online可以通过下面修改

SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY=

SET GLOBAL ENFORCE_GTID_CONSISTENCY=

 

 

 

gtid_executed=

06ae7c12-fe57-11e6-b021-883fd32565d6:1-4792038936,

14361082-83ae-11e8-8328-883fd32565d6:1-1051075034

执行过的所有GTID集合

ordered_commit flush阶段生成GTID,在commit阶段才计入gtid_executed变量,在内存中实时更新

 

 

gtid_purged=

06ae7c12-fe57-11e6-b021-883fd32565d6:1-4792038936,

14361082-83ae-11e8-8328-883fd32565d6:1-1045677424

由于binlog文件的删除(如purge binary logfiles或者超过expire_logs_days设置)已经丢失的GTID事务,同时在搭建从库时使用set global gtid_purged变量来标识哪些GTID事务已经执行过了。

在MySQL触发清理binlog的情况下,比如purge binary logs to或者超过参数expire_logs_days设置的天数后自动删除,需要将丢失的GTID计入这个变量中

 

 

gtid_owned=

14361082-83ae-11e8-8328-883fd32565d6:1049600466#997780200

 

分配GTID发生在GROUP COMMIT的第一个阶段,也就是flush stage,大概可以描述为:

1、事务过程中,碰到第一条DML语句需要记录Binlog时,分配一段Gtid事件的cache,但不分配实际的GTID

2、事务完成后,进入commit阶段,分配一个GTID并写入Step1预留的Gtid事件中,该GTID必须保证不在gtid_owned集合和gtid_executed集合中。 分配的GTID随后被加入到gtid_owned集合中。

3、将Binlog 从线程cache中刷到Binlog文件中。

4、将GTID加入到gtid_executed集合中。

5、在完成sync stage 和commit stage后,各个会话将其使用的GTID从gtid_owned中移除。

master

show global variables like '%uuid%';

server_uuid=14361082-83ae-11e8-8328-883fd32565d6

server_uuid实际上是一个32字节+1字节(/0)的字符串。

MySQL启动的时候会调用init_server_auto_options() 读取auto.cnf文件。如果没有读取到则调用generate_server_uuid()调用生成一个server_id。

master

select * from mysql.gtid_executed;

source_uuid=14361082-83ae-11e8-8328-883fd32565d6

uuid of the source where the transaction was originally executed.

gtid_executed表是5.7.5以后新增的,是GTID持久化的介质,实例重启后读取这个表初始化。

1、从库在binlog关闭或者binlog开启,参数log_slave_updates关闭的情况,实时将GTID持久化到gtid_executed表中。

2、从库在binlog开启同时参数log_slave_updates开启的情况,不实时更新

3、主库不实时更新。

4、执行reset master时,清空表

5、set global gtid_purged时,设置表

 

 

interval_start=1

First number of interval

同上

 

 

interval_end=1053828634

Last number of interval

同上

master

show master status\G

Executed_Gtid_Set=

06ae7c12-fe57-11e6-b021-883fd32565d6:1-4792038936,

14361082-83ae-11e8-8328-883fd32565d6:1-1054196743

执行过的所有GTID集合

同gtid_executed

master

Mysqlbinlog -vvv SVR14077HW2288-bin.030734|less

Previous-GTIDs=

# 14361082-83ae-11e8-8328-883fd32565d6:1-1055704939,

#e36c6adf-fe56-11e6-9f8e-883fd325654a:74434853-7475373424

Previous gtid Event是包含在每一个binlog的开头用于描述所有以前binlog所包含的全部Gtid的一个集合

(包括已经删除的binlog)

产生的binlog中开头会包含这个值

在5.7中不开启GTID也会包含这个Previous gtid Event

slave

show slave status\G

Master_UUID: 14361082-83ae-11e8-8328-883fd32565d6

对应master的server_uuid

 

 

 

Retrieved_Gtid_Set:14361082-83ae-11e8-8328-883fd32565d6:962977557-1057333221

IO线程已经读取的GTID的集合

1、IO线程收到一个GTID EVENT就会把它加入到Retrieved_Gtid_Set中

2、MySQL重启时会从relay log中初始化Retrieved_Gtid_Set

 

 

Executed_Gtid_Set:06ae7c12-fe57-11e6-b021-883fd32565d6:1-4792038936,

14361082-83ae-11e8-8328-883fd32565d6:1-1057333220

表示SQL线程已经执行的GTID的集合

主库的binlog在从库应用时,会更新Executed_Gtid_Set值

 

https://dev.mysql.com/doc/refman/5.7/en/replication-gtids-lifecycle.html

https://www.tuicool.com/articles/NjqQju

[MySQL 5.6] GTID实现、运维变化及存在的bug

 

本文的主要目的是记下跟gtid相关的backtrace,用于以后的问题排查。另外也会讨论目前在MySQL5.6.11版本中存在的bug。

前言:什么是GTID

 
什么是GTID呢, 简而言之,就是全局事务ID(global transaction identifier ),最初由google实现,官方MySQL在5.6才加入该功能,本文的起因在于5.6引入一大堆的gtid相关变量,深感困惑。
 
去年年中的时候,也写过一片简短的博客,大致介绍了下gtid是什么,  http://mysqllover.com/?p=87  。本文也不打算太多文字的介绍,因为网络上已经有大量的类似文章。
 
GTID的格式类似于:
7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1
 
这是在我的一台服务器上生成的gtid记录,它在binlog中表现的事件类型就是:
GTID_LOG_EVENT:用于表示随后的事务的GTID
 
另外还有两种类型的GTID事件:
ANONYMOUS_GTID_LOG_EVENT :匿名GTID事件类型(暂且不论)
PREVIOUS_GTIDS_LOG_EVENT: 用于表示当前binlog文件之前已经执行过的GTID集合,记录在Binlog文件头,例如:
# at 120 
#130502 23:23:27 server id 119821  end_log_pos 231 CRC32 0x4f33bb48     Previous-GTIDs 
# 10a27632-a909-11e2-8bc7-0010184e9e08:1, 
# 7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1-1129
这个字符串,用“:”分开,前面表示这个服务器的server_uuid,这是一个128位的随机字符串,在第一次启动时生成(函数generate_server_uuid),对应的variables是只读变量server_uuid。 它能以极高的概率保证全局唯一性,并存到文件DATA/auto.cnf中。因此要注意保护这个文件不要被删除或修改,不然就麻烦了。
 
第二部分是一个自增的事务ID号,事务id号+server_uuid来唯一标示一个事务。
 
除了单独的GTID外,还有一个GTID SET的概念。一个GTID SET的表示类似于:
7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1-31
 
GTID_EXECUTED和GTID_PURGED是典型的GTID SET类型变量;在一个复制拓扑中,GTID_EXECUTED 可能包含好几组数据,例如:

mysql> show global variables like ‘%gtid_executed%’\G

*************************** 1. row ***************************
Variable_name: gtid_executed
        Value: 10a27632-a909-11e2-8bc7-0010184e9e08:1-4,
153c0406-a909-11e2-8bc7-0010184e9e08:1-3,
7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1-31,

f914fb74-a908-11e2-8bc6-0010184e9e08:1

 

 

本文讨论的内容包括:

 

一.主库上的gtid产生及记录
二.备库如何使用GTID复制
三.主备运维的变化
四.MySQL5.6.11存在的bug
 

一、主库上的Gtid

 

a.相关变量

主库上每个事务的Gtid包括变化的部分和不变的部分。在讨论之前,要弄清楚GTID维护的四个变量:
 
GTID_PURGED :已经被删除的binlog的事务,它是GTID_EXECUTED的子集,从MySQL5.6.9开始,该变量无法被设置。
GTID_OWNED :  表示正在执行的事务的gtid以及对应的线程ID。
 
例如如下:

mysql> show global variables like ‘%gtid_owned%’\G

*************************** 1. row ***************************
Variable_name: gtid_owned
        Value: 7a07cd08-ac1b-11e2-9fcf-0010184e9e08:11560057#67:11560038#89:11560059#7:11560034#32:11560053#56:11560052#112:11560055#128:11560054#65:11559997#96:11560056#90:11560051#85:11560058#39:11560061#12:11560060#125:11560035#62:11560062#5
1 row in set (0.01 sec)
GTID_EXECUTED  表示已经在该实例上执行过的事务; 执行RESET MASTER 会将该变量置空; 我们还可以通过设置GTID_NEXT执行一个空事务,来影响GTID_EXECUTED
 
GTID_NEXT 是SESSION级别变量,表示下一个将被使用的GTID
 
在内存中也维护了与GTID_PURGED, GTID_OWNED, GTID_EXECUTED相对应的全局对象gtid_state。
gtid_state中维护了三个集合,其中logged_gtids对应GTID_EXECUTED, lost_gtids对应GTID_PURGED,owned_gtids对应GTID_OWNED

b.如何分配和使用GTID

 
在主库执行一个事务的过程中,关于Gtid主要涉及到以下几个部分:
 
事务开始,执行第一条SQL时 ,在写入第一个“BEGIN” 的QUERY EVENT 之前, 为binlog cache 的Group_cache中分配一个group(Group_cache::add_logged_group),并写入一个Gtid_log_event,此时并未为其分配事务id,backtrace 如下:
handler::ha_write_row->binlog_log_row->write_locked_table_maps->THD::binlog_write_table_map->binlog_start_trans_and_stmt->binlog_cache_data::write_event->Group_cache::add_logged_group
暂时还不清楚什么时候一个事务里会有多个gtid的group_cache.
 
在binlog group commit的flush阶段 
 
第一步,调用Group_cache::generate_automatic_gno来为当前线程生成一个gtid,分配给thd->owned_gtid,并加入到owned_gtids中,backtrace如下:
MYSQL_BIN_LOG::process_flush_stage_queue->MYSQL_BIN_LOG::flush_thread_caches->binlog_cache_mngr::flush->binlog_cache_data::flush->gtid_before_write_cache->Group_cache::generate_automatic_gno->Gtid_state::acquire_ownership->Owned_gtids::add_gtid_owner 
 
也就是说,直到事务完成,准备把binlog刷到binlog cache时,才会去为其分配gtid.
当gtid_next的类型为AUTOMATIC时,调用generate_automatic_gno生成事务id(gno),分配流程大概如下:
1.gtid_state->lock_sidno(automatic_gtid.sidno) , 为当前sidno加锁,分配过程互斥
2.gtid_state->get_automatic_gno(automatic_gtid.sidno); 获取事务ID
        |–>初始化候选(candidate)gno为1
        |–>从logged_gtids[$sidno]中扫描,获取每个gno区间(iv):
               |–>当candidate < iv->start(或者MAX_GNO,如果iv为NULL)时,判断candidate是否有被占用,如果没有的话,则使用该candidate,从函数返回,否则candidate++,继续本步骤
        |–>将candidate设置为iv->end,iv指向下一个区间,继续第二步
        从该过程可以看出,这里兼顾了区间存在碎片的场景,有可能分配的gno并不是全局最大的gno. 不过在主库不手动设置gtid_next的情况下,我们可以认为主库上的gno总是递增的。
 
3.gtid_state->acquire_ownership(thd, automatic_gtid);
         |–>加入到owned_gtids集合中(owned_gtids.add_gtid_owner),并赋值给thd->owned_gtid= gtid
4.gtid_state->unlock_sidno(automatic_gtid.sidno);  解锁
第二步, 调用Gtid_state::update_on_flush将当前事务的grid加入到logged_gtids中,backtrace如下:
MYSQL_BIN_LOG::process_flush_stage_queue->MYSQL_BIN_LOG::flush_thread_caches->binlog_cache_mngr::flush->binlog_cache_data::flush->MYSQL_BIN_LOG::write_cache->Gtid_state::update_on_flush 
在bin log group commit的commit阶段
 
调用Gtid_state::update_owned_gtids_impl 从owned_gtids中将当前事务的gtid移除,backtrace 如下:
MYSQL_BIN_LOG::ordered_commit->MYSQL_BIN_LOG::finish_commit->Gtid_state::update_owned_gtids_impl 
 
上述步骤涉及到的是对logged_gtids和owned_gtids的修改。而lost_gtids除了启动时维护外,就是在执行Purge操作时维护。
 
例如,当我们执行purge binary logs to ‘mysql-bin.000205′ 时, mysql-bin.index先被更新掉,然后再根据index文件找到第一个binlog文件的PREVIOUS_GTIDS_LOG_EVENT事件,更新lost_gtids集合,backtrace如下:
purge_master_logs->MYSQL_BIN_LOG::purge_logs->MYSQL_BIN_LOG::init_gtid_sets->read_gtids_from_binlog->Previous_gtids_log_event::add_to_set->Gtid_set::add_gtid_encoding->Gtid_set::add_gno_interval 
 
关于binlog group commit,参见之前写的博客:  http://mysqllover.com/?p=581  

c.如何持久化GTID

当重启MySQL后,我们看到GTID_EXECUTED和GTID_PURGED和重启前是一致的。
 
持久化GTID,是通过全局对象gtid_state来管理的。gtid_state在系统启动时调用函数gtid_server_init分配内存;如果打开了binlog,则会做进一步的初始化工作:
 
quoted code:

5419       if (mysql_bin_log.init_gtid_sets(

5420             const_cast<Gtid_set *>(gtid_state->get_logged_gtids()),
5421             const_cast<Gtid_set *>(gtid_state->get_lost_gtids()),
5422             opt_master_verify_checksum,
5423             true/*true=need lock*/))
5424         unireg_abort(1);
 
gtid_state 包含3个gtid集合:logged_gtids, lost_gtids, owned_gtids,前两个都是gtid_set类型, owned_gtids类型为Owned_gtids
 
MYSQL_BIN_LOG::init_gtid_sets 主要用于初始化logged_gtids和lost_gtids,该函数的逻辑简单描述下:
 
1.扫描mysql-index文件,搜集binlog文件名,并加入到filename_list中
2.从最后一个文件开始往前读,依次调用函数read_gtids_from_binlog:
      |–>打开binlog文件,如果读取到PREVIOUS_GTIDS_LOG_EVENT事件
          (1)无论如何,将其加入到logged_gtids(prev_gtids_ev->add_to_set(all_gtids))
          (2)如果该文件是第一个binlog文件,将其加入到lost_gtids(prev_gtids_ev->add_to_set(prev_gtids))中.
     
      |–>获取GTID_LOG_EVENT事件
          (1) 读取该事件对应的sidno,sidno= gtid_ev->get_sidno(false);
               这是一个32位的整型,用sidno来代表一个server_uuid,从1开始计算,这主要处于节省内存的考虑。维护在全局对象global_sid_map中。
               当sidno还没加入到map时,调用global_sid_map->add_sid(sid),sidno从1开始递增。
 
          (2) all_gtids->ensure_sidno(sidno)
               all_gtids是gtid_set类型,可以理解为一个集合,ensure_sidno就是要确保这个集合至少可以容纳sidno个元素
 
          (3) all_gtids->_add_gtid(sidno, gtid_ev->get_gno()  
               将该事件中记录的gtid加到all_gtids[sidno]中(最终调用Gtid_set::add_gno_interval,这里实际上是把(gno, gno+1)这样一个区间加入到其中,这里
               面涉及到区间合并,交集等操作    )
当第一个文件中既没有PREVIOUS_GTIDS_LOG_EVENT, 也没有GTID_LOG_EVENT时,就继续读上一个文件
如果只存在PREVIOUS_GTIDS_LOG_EVENT事件,函数read_gtids_from_binlog返回GOT_PREVIOUS_GTIDS
如果还存在GTID_LOG_EVENT事件,返回GOT_GTIDS
 
这里很显然存在一个问题,即如果在重启前,我们并没有使用gtid_mode,并且产生了大量的binlog,在这次重启后,我们就可能需要扫描大量的binlog文件。这是一个非常明显的Bug, 后面再集中讨论。
 
3.如果第二部扫描,没有到达第一个文件,那么就从第一个文件开始扫描,和第2步流程类似,读取到第一个PREVIOUS_GTIDS_LOG_EVENT事件,并加入到lost_gtids中。
 
简单的讲,如果我们一直打开的gtid_mode,那么只需要读取第一个binlog文件和最后一个binlog文件,就可以确定logged_gtids和lost_gtids这两个GTID SET了。
 

二、备库上的GTID

a.如何保持主备GTID一致

由于在binlog中记录了每个事务的GTID,因此备库的复制线程可以通过设置线程级别GTID_NEXT来保证主库和备库的GTID一致。
 
默认情况下,主库上的thd->variables.gtid_next.type为AUTOMATIC_GROUP,而备库为GTID_GROUP
 
备库SQL线程gtid_next输出:
(gdb) p thd->variables.gtid_next 
$2 = { 
type = GTID_GROUP, 
gtid = { 
sidno = 2, 
gno = 1127, 
static MAX_TEXT_LENGTH = 56 
}, 
static MAX_TEXT_LENGTH = 56 
}
 
这些变量在执行Gtid_log_event时被赋值:Gtid_log_event::do_apply_event,大体流程为:
1.rpl_sidno sidno= get_sidno(true);  获取sidno
2.thd->variables.gtid_next.set(sidno, spec.gtid.gno);  设置gtid_next
3.gtid_acquire_ownership_single(thd); 

     |–>检查该gtid是否在logged_gtids集合中,如果在的话,则返回(gtid_pre_statement_checks会忽略该事务)

     |–>如果该gtid已经被其他线程拥有,则等待(gtid_state->wait_for_gtid(thd, gtid_next)),否则将当前线程设置为owner(gtid_state->acquire_ownership(thd, gtid_next))
 
在上面提到,有可能当前事务的GTID已经在logged_gtids中,因此在执行Rows_log_event::do_apply_event或者mysql_execute_command函数中,都会去调用函数gtid_pre_statement_checks
该函数也会在每个SQL执行前,检查gtid是否合法,主要流程包括:
1.当打开选项enforce_gtid_consistency时,检查DDL是否被允许执行(thd->is_ddl_gtid_compatible()),若不允许,返回GTID_STATEMENT_CANCEL
2.检查当前SQL是否会产生隐式提交并且gtid_next被设置(gtid_next->type != AUTOMATIC_GROUP),如果是的话,则会抛出错误ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET 并返回GTID_STATEMENT_CANCEL,注意这里会导致bug#69045
3.对于BEGIN/COMMIT/ROLLBACK/(SET OPTION 或者 SELECT )且没有使用存储过程/ 这几种类型的SQL,总是允许执行,返回GTID_STATEMENT_EXECUTE
4.gtid_next->type为UNDEFINED_GROUP,抛出错误ER_GTID_NEXT_TYPE_UNDEFINED_GROUP,返回GTID_STATEMENT_CANCEL
5.gtid_next->type == GTID_GROUP且thd->owned_gtid.sidno == 0时, 返回GTID_STATEMENT_SKIP
 
其中第五步中处理了函数gtid_acquire_ownership_single的特殊情况
 

b.备库如何发起DUMP请求

引入GTID,最大的好处当然是我们可以随心所欲的切换主备拓扑结构了。在一个正常运行的复制结构中,我们可以在备库简单的执行如下SQL:

 
CHANGE MASTER TO MASTER_USER=’$USERNAME’, MASTER_HOST=’ ‘, MASTER_PORT=’ ‘, MASTER_AUTO_POSITION=1;
 
打开GTID后,我们就无需指定binlog文件或者位置,MySQL会自动为我们做这些事情。这里的关键就是MASTER_AUTO_POSITION。IO线程连接主库,可以大概分为以下几步:
1.IO线程在和主库建立TCP链接后,会去获取主库的uuid(get_master_uuid),然后在主库上设置一个用户变量@slave_uuid(io_thread_init_commands)
 
2.之后,在主库上注册SLAVE(register_slave_on_master)
 
在主库上调用register_slave来注册备库,将备库的host,user,password,port,server_id等信息记录到slave_list哈希中。
 
3.调用request_dump,开始向主库请求数据,这里分两种情况:
MASTER_AUTO_POSITION=0时,向主库发送命令的类型为COM_BINLOG_DUMP,这是传统的请求BINLOG的模式
MASTER_AUTO_POSITION=1时,命令类型为COM_BINLOG_DUMP_GTID,这是新的方式。
这里我们只讨论第二种。第二种情况下,会先去读取备库已经执行的gtid集合
quoted code in rpl_slave.cc :

2974   if (command == COM_BINLOG_DUMP_GTID)

2975   {
2976     // get set of GTIDs
2977     Sid_map sid_map(NULL/*no lock needed*/);
2978     Gtid_set gtid_executed(&sid_map);
2979     global_sid_lock->wrlock();
2980     gtid_state->dbug_print();
2981     if (gtid_executed.add_gtid_set(mi->rli->get_gtid_set()) != RETURN_STATUS_OK ||
2982         gtid_executed.add_gtid_set(gtid_state->get_logged_gtids()) !=

2983         RETURN_STATUS_OK)

 

 
构建完成发送包后,发送给主库。
 

在主库上接受到命令后,调用入口函数com_binlog_dump_gtid,流程如下:

 

1.slave_gtid_executed.add_gtid_encoding(packet_position, data_size) ;读取备库传来的GTID SET 
2.读取备库的uuid(get_slave_uuid),被根据uuid来kill僵尸线程(kill_zombie_dump_threads)
这也是之前SLAVE IO线程执行SET @SLAVE_UUID的用处。
3.进入mysql_binlog_send函数:
         |–>调用MYSQL_BIN_LOG::find_first_log_not_in_gtid_set,从最后一个Binlog开始扫描,获取文件头部的PREVIOUS_GTIDS_LOG_EVENT,如果它是slave_gtid_executed的子集,保存当前binlog文件名,否则继续向前扫描。
         这一步的目的就是为了找出备库执行到的最后一个Binlog文件。
         
         |–>从这个文件头部开始扫描,遇到GTID_EVENT时,会去判断该GTID是否包含在slave_gtid_executed中:
                         Gtid_log_event gtid_ev(packet->ptr() + ev_offset,
                                 packet->length() – checksum_size,
                                 p_fdle);
                          skip_group= slave_gtid_executed->contains_gtid(gtid_ev.get_sidno(sid_map),
                                                     gtid_ev.get_gno());
         主库通过GTID决定是否可以忽略事务,从而决定执行开始的位置 
 
 
注意,在使用MASTER_LOG_POSITION后,就不要指定binlog的位置,否则会报错。

三、运维操作

 

a.如何忽略复制错误

 
当备库复制出错时,传统的跳过错误的方法是设置sql_slave_skip_counter,然后再START SLAVE。
但如果打开了GTID,就会设置失败:
 

mysql> 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

 

 
提示的错误信息告诉我们,可以通过生成一个空事务来跳过错误的事务。
 
我们手动产生一个备库复制错误:
 
Last_SQL_Error: Error ‘Unknown table ‘test.t1” on query. Default database: ‘test’. Query: ‘DROP TABLE `t1` /* generated by server */’
 
查看binlog中,该DDL对应的GTID为7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1131
 
在备库上执行:

mysql> STOP SLAVE;

Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION GTID_NEXT = ’7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1131′;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN; COMMIT;
Query OK, 0 rows affected (0.00 sec)
 

Query OK, 0 rows affected (0.00 sec)

 

mysql> SET SESSION GTID_NEXT = AUTOMATIC;

Query OK, 0 rows affected (0.00 sec)

 

mysql> START SLAVE;
 
再查看show slave status,就会发现错误事务已经被跳过了。这种方法的原理很简单,空事务产生的GTID加入到GTID_EXECUTED中,这相当于告诉备库,这个GTID对应的事务已经执行了。

b.重指主库

使用change master to …. , MASTER_AUTO_POSITION=1;
注意在整个复制拓扑中,都需要打开gtid_mode
 

c.新的until条件

5.6提供了新的util condition,可以根据GTID来决定备库复制执行到的位置
SQL_BEFORE_GTIDS:在指定的GTID之前停止复制
SQL_AFTER_GTIDS :在指定的GTID之后停止复制
 
判断函数为Relay_log_info::is_until_satisfied
 
 

d.适当减小binlog文件的大小

如果开启GTID,理论上最好调小每个binlog文件的最大值,以缩小扫描文件的时间。

四、存在的bug

bug#69097 , 即使关闭了gtid_mode,也会在启动时去扫描binlog文件。
当在重启前没有使用gtid_mode,重启后可能会去扫描所有的binlog文件,如果Binlog文件很多的话,这显然是不可接受的。
 
bug#69096 ,无法通过GTID_NEXT_LIST来跳过复制错误,因为默认编译下,GTID_NEXT_LIST未被编译进去。
TODO:GTID_NEXT_LIST的逻辑上面均未提到,有空再看。
 
bug#69095 ,将备库的复制模式设置为STATEMENT/MIXED。 主库设置为ROW模式,执行DML 会导致备库复制中断
 
Last_SQL_Error: Error executing row event: ‘Cannot execute statement: impossible to write to binary log since statement is in row format and BINLOG_FORMAT = STATEMENT.’
 
判断报错的backtrace:
handle_slave_worker->slave_worker_exec_job->Rows_log_event::do_apply_event->open_and_lock_tables->open_and_lock_tables->lock_tables->THD::decide_logging_format
 

解决办法:将备库的复制模式设置为’ROW’ ,保持主备一致

 

该bug和GTID无关
 
bug#69045 , 当主库执行类似 FLUSH PRIVILEGES这样的动作时,如果主库和备库都开启了gtid_mode,会导致复制中断
Last_SQL_Error: Error ‘Cannot execute statements with implicit commit inside a transaction when @@SESSION.GTID_NEXT != AUTOMATIC or @@SESSION.GTID_NEXT_LIST != NULL.’ on query. Default database: ”. Query: ‘flush privileges’
 
也是一个很低级的bug,在MySQL5.6.11版本中,如果有可能导致隐式提交的事务, 则gtid_next必须等于AUTOMATIC,对备库复制线程而言,很容易就中断了,判断逻辑在函数gtid_pre_statement_checks中
 

转载于:https://www.cnblogs.com/DataArt/p/10248242.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30481087/article/details/99958742

智能推荐

mmwave_weixin_34413103的博客-程序员秘密

毫米波(mmWave)致力于支持5G应用创新开发,集成在BEEcube BEE7基带平台上的赛灵思256QAM毫米波调制解调器IP为宽带回程原型设计提供完整的开箱即用型解决方案赛灵思公司 (NASDAQ: XLNX) 携手BEEcube联合宣布推出全球首款毫米波(mmWave)原型设计平台解决方案,支持5G应用的创新开发。赛灵思的最新 256QAM 500MHz mmWave 调制解调器IP结...

【已解决】LaTeX调整图片大小_latex 图大小-程序员秘密

在LaTeX插入图片的时候,经常需要调整图片的大小。我们可以通过如下代码来完成:\begin{figure}[htb] \centering \includegraphics[width=0.6\linewidth]{fig2.png} \caption{图片的解释}\end{figure}其中,width=0.6\linewidth 表明将插入的图像等比例缩小至0.6倍。经验证,调整比例后图像成功地缩小了。...

关于二分算法_楚西文的博客-程序员秘密

声明:关于二分算法一直无法理解,直到看了某大佬的模板,令我醍醐灌顶,感谢张佬!先上代码#include&lt;bits/stdc++.h&gt;using namespace std;const int N=1e6+10; //数组大小,由题进行赋值,+10可以防止数组越界int a[N]; int main(){ int m,n; scanf("%d %d",&amp;m,&amp;n); for(int i=0;i&lt;m;i++)scanf("%d"

MCS-51单片机I/O端口的存取_mybirdsky的博客-程序员秘密

2007-12-25 10:38:53 MCS-51单片机通常有4个8位I/O端口, 向各端口的写数据均写入到对应端口的锁存器中, 但对各端口的读操作却有两个方式:读锁存器和读引脚1 读-修改-写操作 Pn(指P0,P1,P2,P3)在51汇编语言中是特殊的标识符,既代表Pn端口引脚,又代表Pn锁存器(Pn SFR)。在MCS-51指令系统中有些指令读锁存器的值,

7.文件I/O_coeus7的博客-程序员秘密

目录7.1 文件综述7.2 文件I/O基本操作7.2.1文件描述符7.2.2 创建、打开、关闭文件7.3 读写与定位文件7.3.1 read函数7.3.2 write函数7.3.3 lseek7.3.4 例程7.4 文件共享7.4.1 pread和pwrite7.4.2 dup和dup27.4.3 sync函数7.4.4 fcntl函数7...

android studio 解析Excel数据格式导入poi-3.17.jar时的一系列报错及处理Failed resolution of: Ljavax/xml/stream/XMLEventFa..._djh10000的博客-程序员秘密

在org官网下载的poi jar包,导入到studiocompile files('libs/poi-3.17.jar')compile files('libs/poi-ooxml-3.17.jar')compile files('libs/poi-ooxml-schemas-3.17.jar')compile files('libs/xmlbeans-2.6.0.jar')如果...

随便推点

PHP实现URL地址跳转的几种方法代码_CDSN大帝的博客-程序员秘密

实例:一行URL跳转代码    2.     $url = $_GET['url'];    Header("Location:$url");    ?>如保存为aaa.php,可以实现aaa.php?url=www.baidu.com跳转到百度的效果当用户访问zhuce.php时,判断一个cookie是否存在,如果存在就跳转到register.php,

Swift中两种桥接头文件创建方式_bainei6473的博客-程序员秘密

桥接头文件主要应用于swift和oc的混编。这里有两种创建方式。方法一:适用于项目(swift项目)之前没创建过oc的类,或(oc的项目)swif的类这里以swift项目为例既然没用过就创建一个呗Commond+n然后创建,这是会出来一个提示选择Create Bridging Header就可以了,这样项目中就有这个桥接头文件了。在桥接头文件里直接 impor...

mybatis-plus/mybatis的组件们——拦截器、字段填充器、类型处理器、表名替换、SqlInjector(联合主键处理)_mybatis plus abstractsqlparserhandler_LL小蜗牛的博客-程序员秘密

最近有个练手的小例子,大概就是配置两个数据源,从一个数据源读取数据写到另一个数据源,虽然最后做了出来,但是不支持事务。。。就当是对mybatis-plus/mybatis组件使用方式的记录吧,本次例子使用的仍是mybatis-plus回忆一下mybatis核心对象:Configuration 初始化基础配置,比如MyBatis的别名等,一些重要的类型对象,如,插件,映射器,ObjectFa...

matlab求c12,采用FPGA芯片EP1C12Q240C8实现直接数字频率合成器的设计_吴厝坑的博客-程序员秘密

1、引言自1971年,美国学者J.Tierney等人撰写的“A Digital Frequency Synthesizer”-文首次提出了以全数字技术实现数字频率合成以来,构成DDS元器件的速度的限制和数字化引起的噪声这两个主要缺点阻碍了DDS的发展与实际应用。近几年超高速数字电路的发展尤其是大规模超高速的FPGA技术的日渐成熟,以及对DDS的深入研究,DDS的最高工作频率以及噪声性能已接近并达到...

LINUX部署项目在jboss上的注意事项_巴克的博客-程序员秘密

1.  log4jConfigLocation     classpath:config/config-log4j.properties          这里的classpath:config/config-log4j.properties 中应该使用的

华为任正非写的《致新员工书》_误凡尘的博客-程序员秘密

您有幸进入了华为公司。我们也有幸获得了与您的合作。我们将在共同信任的基础上,度过您在公司工作的岁月。这种理解和信任是愉快奋斗的桥梁与纽带。  华为公司是一个以高技术为起点,着眼于大市场、大系统、大结构的高科技企业。以它的历史使命,它需要所有的员工必需坚持合作,走集体奋斗的道路。没有这一种平台,你的聪明才智是很难发挥,并有所成就的。因此,没有责任心,不善于合作,不能集体奋斗的人,等于丧失了在华为进...

推荐文章

热门文章

相关标签