在日常运维中,GTID带来的最方便的作用就是搭建和维护主从复制,这也是DBA日常工作中最经常的操作了。GTID的主从模式替代了MySQL前期版本中利用二进制日志文件的名称和日志位置的做法,使用GTID使操作和维护都变得更加简洁和可靠。
搭建主从时,需要注意的MySQL参数
相对传统模式搭建主从来说,GTID搭建主从模式需要在配置文件中指定如下主要参数。
-
server_id:设置MySQL实例的server_id,每个实例的server_id不能一样。
-
gtid_mode=ON:MySQL实例开启GTID模式。
-
enforce_gtid_consitency=ON:使用GTID模式复制时,需要开启此参数,用来保证GTID的一致性。
-
log-bin:MySQL必须开启Binlog。
-
log-slave-updates=1:决定SLAVE从MASTER接收到的更新且执行完之后,执行的Binlog是否记录到SLAVE的Binlog中,建议开启。
-
binlog_format=ROW:强烈建议 binlog_format 使用ROW格式,其他格式可能造成数据不一致。
-
skip-slave-start=1:当SLAVE数据库启动的时候,SLAVE不会自动开启复制。
开启GTID
-
如果数据库已经启动,在MySQL5.7.6之前需要重启MySQL数据库。步骤如下。
第1步:关闭MASTER的写入,保证SLAVE与MASTER的数据是同步的。 第2步:在SLAVE上配置参数skip-slave-start=1,避免SLAVE启动后,继续使用传统的复制模式。 第3步:修改配置,开启GTID模式,具体参数如上所述。 第4步:重启所有的MySQL数据库。
注意:在MySQL5.7.6之后可以在线调整,无须重启数据库,具体操作后面会讲述。在这个版本之前,因为主从节点必须要同时开启或同时关闭,所以导致在线升级GTID不可行,只能重启,如果有这样的需求,需要考虑与业务的配合。
- 如果数据库是新搭建的,只需要配置上面的参数,然后启动数据库即可。这样写入事务在Binlog中都将带有GTID信息。
搭建主从
根据MASTER的情况,常见的基于GTID模式的主从搭建有以下几种方式(复制的相关账号和权限都已经授予,并且GTID已经开启)。
-
MASTER是新搭建的且无数据∶针对这种情况,直接利用CHANGEMASTER语句搭建。
mysql> CHANGE MASTER TO MASTER_HOST='****',MASTER_PORT=***,MASTER_USER=***',\ -> MASTER PASSWORD='***',MASTER_AUTO_POSITION=1;
-
MASTER运行不久,所有的Binlog保留完整∶针对这种情况,可以使用类似上面的方式搭建,直接利用CHANGEMASTER语句,从MASTER上获取所有的GTID,然后在SLAVE上执行。优点是简单快捷,缺点是如果Binlog相对较多,SLAVE同步时间相对较长,可能导致网络压力过大。
-
MASTER具有大量数据∶针对这种情况,可能不能使用上面的第二种方法,因为最原始的Binlog可能已经被删除了,无法从头开始获取所有的GTID信息。那么需要从MASTER 上获取数据及该数据的GTID范围,然后通过在SLAVE上设置选项(gtid_purged)的方式来跳过这些GTID。最后通过CHANGE MASTER的方式搭建主从,具体操作方法如下。
-
利用备份方式获取MASTER的数据及GTID范围,包含了gtid_purged=‘uuid∶interval[-interval]’。使用innobackupex备份会将该信息保留在xtrabackup_binlog_info文件中。
-
利用备份的数据,搭建SLAVE实例。
-
启动SLAVE实例,并且设置gtid_purged的值,跳过这段范围。命令为∶
SET @@GLOBAL.GTID_PURGED='uuuid:interval[-interval]'
-
利用CHANGE MASTER语句,配置主从复制。
-
启动SLAVE复制,SLAVE会自动跳过这段GTID范围,拉取最新的GTID信息。
-
使用GTID案例总结
如何跳过一个GTID
在复制中,偶尔会遇到主键冲突或从库找不到该条记录等错误。那么如何解决呢?
在传统的复制模式中,经常通过设置 sql_slave_skip_counter参数跳过一个事件,如下。
mysql> SET GLOBAL sql_slave_skip_counter = 1;
但是在GTID模式中,如果继续执行上述操作,就会有如下错误产生。
ERROR 1858(HY000): sql_slave_skip_countercan not be set when the server is running
with @@GLOBAL.GTID_MODE = ON.Instead,foor each transaction that you want to skip,
generate an empty transaction with the same GTID as the transaction
在GTID模式的复制情况下,如果SLAVE发生错误,则可以通过跳过该事务的方式恢复主从复制,如图13.1所示。
从图13.1中可以看出,出错事务的Binlog文件为mysql-bin.000003,end_log_pos为426,其开始位置为194。那么,可以去主库上分析一下 Binlog,看一下发生冲突的事务是哪个。查看MASTER的Binlog信息如图13.2所示。
可以看出发现冲突的事务号为b2a4fb9a-dc57-11e6-a8f9-fa163e79be41∶8,当然要确定是哪一个事务发生了冲突,还可以直接从show slave status;结果中通过比对的方式找到冲突的位置。严谨起见,通过对Binlog内容分析得知冲突事务是插入了一条数据,主键为4。在从库中查看这条记录是否真的存在,如下。
发现 SLAVE 中已经存在这条记录了。这时,可以通过跳过该事务的方式来放弃该事务在SLAVE上的执行,使SLAVE能够正常运行。基于GTID模式的复制,跳过一个事务,需要利用一个空事务来完成。
`
此时可以想象一下,如果删除数据时,因为找不到对应记录(Binlog为ROW模式)而导致复制中断,该如何处理如果是其他的错误呢,又该如何处理呢还有为什么会出现这样的问题,是不是从库可写了?具体问题还需要具体分析,这里的主要目的是要说明如何在GTID复制模式下跳过一个事务。
跳过事务,使SLAVE正常运行,这种方式虽然有时候在处理问题时,确实很方便,但也是非常危险的,因为可能会出现事务不一致的情况。所以,在跳过之前,分析一下 Binlog并且记录下来,分析是否可以跳过。跳过之后,看一下主从数据是否一致,是否需要修复数据等。总之,需要具体问题具体分析,请谨慎使用。
利用GTID模式快速改变主从复制关系
在日常数据库运维中,经常需要调整复制的拓扑关系,例如原有的拓扑关系,如图13.3所示。
在维护过程中,需要将从库节点C的复制源修改为B,即将复制拓扑修改为级联关系∶A->B ->C。具体操作如下。
在START SLAVE操作之后,C节点与B节点之间的交互过程如下。
- C节点向B节点发起一个Dump Binlog请求,并将自身已经执行的GTID集合信息一起发送给B节点。
- B节点通过对比接收到C节点发送过来的GTID集合,将C节点未执行过的Binlog信息发送给C节点。
- C节点获取到未执行的Binlog信息,然后应用这些Binlog信息。在这个过程中,B节点也会不断地发送最新的数据库修改信息(Binlog信息)给C节点。
- C节点不断应用这些Binlog信息,最终实现C节点与B节点同步。
基于GTID复制,DBA可以在线快速调整复制的拓扑关系,只需要调整复制节点的基本信息,不需要手动寻找复制点,方便DBA在线维护。
在线将传统模式复制改为GTID模式复制
基于传统复制的数据库想要调整为基于GTID的模式复制,在老版本中是需要停服务的,这对线上环境要求比较高。但是,在MySQL5.7.6版本及更新的版本中,已经支持在线修改GTID模式,可以在线将传统复制调整到基于GTID的模式复制了。具体步骤与相关说明如下。
-
在每一台服务器上设置ENFORCE_GTID_CONSISTENCY=WARN。
SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = WARN;
这一步设置之后,使得所有事务都允许违反GTID的一致性。
-
在每一台服务器上设置ENFORCE_GTID_CONSISTENCY=ON。以确保所有的事务都不能违反GTID的一致性。
SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = ON;
-
在每一台服务器上设置GTID_MODE=OFF_PERMISSIVE。
SET @@GLOBAL.GTID_MODE = OFF_PERMISSIVE;
这一步表示,新的事务是匿名的,同时允许复制的事务是GTID或是匿名的。
注意∶需要确保这一步操作在所有的服务器上都执行。
-
在每一台服务器上设置GTID_MODE=ON_PERMISSIVE。
SET @qGLOBAL.GTID_MODE = ON_PERMISSIVE;
这一步表示,新的事务使用GTID,同时允许复制的事务为GTID或匿名事务。
注意∶需要确保这一步操作在所有的服务器上都执行。
-
等待 ONGOING ANONYMOUS_TRANSACTION_COUNT状态值为0。
SHOW STATUS LIKE 'ONGOING_ANONYMOUS_TRANSACTION_COUNT';
在所有从库上查询该状态,必须要为0才能进行下一步。该状态表示已标记为匿名的正在进行的事务数量,如果状态值为0表示无事务等待被处理。
-
在每一台服务上设置GTID_MODE=ON。
SET @@GLOBAL.GTID_MODE = ON;
在所有服务器上,开启GTID。
-
修改 my.cnf的配置,配置修改后,即使数据库重启,配置也是生效的。
enforce_gtid_consistency =1 gtid_mode=ON*
上述操作是开启GTID,但是复制还是基于Binlog位置的,可以通过将选项MASTER_AUTO_POSITION设置为1,将复制调整为基于GTID模式的复制,具体操作如下。
mysql> STOP SLAVE; mysql> CHANGE MASTER TO MASTER_AUTO_POSITION=1; mysql> START SLAVE;
上文已经描述了如何在线开启GTID,并且将传统模式复制改为GTID模式复制。
GTID模式复制改为传统模式复制
接下来简单介绍如何在GTID模式复制下,改为传统模式复制,并且关闭GTID,而不用停止服务,且不影响线上业务等。具体操作步骤与说明如下。
-
关闭基于GTID模式的复制,调整为传统复制。
mysql> STOP SLAVE;
假设复制关闭后,复制的位置为mysql-bin.000006∶518。通过将选项MASTER_AUTO_POSITION设置为0,调整为基于Binlog位置的复制。
mysql> CHANGE MASTER TO MASTER AUTO POSITION=0, MASTER_LOG_FILE='mysql-bin.000006', MASTER_LOG_POS=518; mysql> START SLAVE;
-
在每一台服务器上设置GTID模式为ON_PERMISSIVE。
SET @@GLOBAL.GTID_MODE = ON_PERMISSIVE;
-
在每一台服务器上设置GTID模式为OFF_PERMISSIVE。
SET @@GLOBAL.GTID MODE = OFF_PERMISSIVE;
-
等待所有的服务器上的变量@@GLOBALGTID_OWNED为空,它表示正在由线程执行的全局GTID集合。
-
等待所有的SLAVE上都复制完成匿名事务。
-
在每一台服务器关闭GTID。
SET @@GLOBAL.GTID MODE = OFF;
-
修改配置文件,配置完成以后,即使数据库重启,配置也是生效的。
gtid_mode=OFF enforce_gtid_consistency=OFF
GTID的限制
由于基于GTID的复制依赖于事务,所以在使用GTID时,有些MySQL特性不支持,如下。
1. 事务中混合多个存储引擎,会产生多个GTID。
当使用GTID时,如果在同一个事务中,更新包括了非事务引擎(如MyISAM)和事务引擎(如InnoDB)表的操作,就会导致多个GTID分配给同一个事务。主从库的表存储引擎不一致,会导致数据不一致。
如果主从库的存储引擎不一致,例如一个是事务存储引擎,一个是非事务存储引擎,则会导致事务和GTID之间一对一的关系被破坏,结果导致基于GTID的复制不能正确地运行。
2. 基于GTID模式复制,不支持CREATETABLE…SELECT语句。
因为使用基于行模式的复制时,该语句实际上被记录为两个单独的事件,一个是创建表,另一个是将原表中的数据插入到刚刚创建的新表中。当在事务中执行该语句时,在一些情况下,这两个事务可能接收到相同的事务ID,这意味着包含插入的事务将被从库跳过。因此,在使用基于GTID的复制时,不支持CREATE TABLE…SELECT
mysql> create table t2(id int not null auto_increment, user_en varchar(20) not null default '',
primary key(id)) as select id, name en from t1;
ERROR 1786(HY000):Statement violates GTID consistency:CREATE TABLE ...SELECT.
3. 不支持CREATE_TEMPORARY_TABLE和DROP_TEMPORARY_TABLE。
使用GTID复制时,不支持CREATETEMPORARYTABLE和DROPTEMPORARYTABLE。但是在 autocommit=1的情况下可以创建临时表,MASTER 创建临时表不产生GTID信息,所以不会同步到SLAVE上,但是删除临时表时,产生GTID会导致主从中断。
4. 不推荐在GTID模式的实例上进行mysql_upgrade。
因为mysql_upgrade的过程要创建或修改系统表(非事务引擎),所以不建议在开启GTID 模式的实例上使用带有一write-binlog选项的mysql_upgrade。