MySQL基于GTID复制模式小结
一、GTID概念介绍
GTID是mysql5.6版本出来的新特性
GTID即全局事务ID (global transaction identifier), 其保证为每一个在主上提交的事务在复制集群中可以生成一个唯一的ID。mysql主从结构在一主一从情况下对于GTID来说就没有优势了,而对于2台主以上的结构优势异常明显,可以在数据不丢失的情况下切换新主。
使用GTID需要注意: 在构建主从复制之前,在一台将成为主的实例上进行一些操作(如数据清理等),通过GTID复制,这些在主从成立之前的操作也会被复制到从服务器上,引起复制失败。也就是说通过GTID复制都是从最先开始的事务日志开始,即使这些操作在复制之前执行。比如在server1上执行一些drop、delete的清理操作,接着在server2上执行change的操作,会使得server2也进行server1的清理操作。
GTID实际上是由UUID+TID (即transactionId)组成的。其中UUID(即server_uuid) 产生于auto.conf文件(cat /iddbs/mysql/data/auto.cnf),是一个MySQL实例的唯一标识。TID代表了该实例上已经提交的事务数量,并且随着事务提交单调递增,所以GTID能够保证每个MySQL实例事务的执行(不会重复执行同一个事务,并且会补全没有执行的事务)。GTID在一组复制中,全局唯一。 下面是一个GTID的具体形式 :
mysql> select @@server_uuid;
+--------------------------------------+
| @@server_uuid |
+--------------------------------------+
| 09d4c484-afc6-11eb-a6fd-000c293bb7d1 |
+--------------------------------------+
mysql> show master status;
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------------+
| mysql_bin.000012 | 236 | | | 09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-2,
f9e37b40-a26e-11eb-9b22-000c293bb7d1:1-990216 |
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------------+
GTID:09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-2
UUID:09d4c484-afc6-11eb-a6fd-000c293bb7d1
transactionId: 1-2
在整个复制架构中GTID 是不变化的,即使在多个连环主从中也不会变。
例如:ServerA --->ServerB ---->ServerC
GTID从在ServerA ,ServerB,ServerC 中都是一样的
了解了GTID的格式,通过uuid就能刚确认事务在哪个实例上提交的。通过GTID可以既方便的进行复制结构上的故障转移,新主设置,可以很好解决如下图所示的问题:
如果是传统的基于binlog file 和pos的主从复制, 如图, Server1(Master)崩溃,根据从上show slave status获得Master_log_File/Read_Master_Log_Pos的值,Server2(Slave)已经跟上了主,Server3(Slave)没有跟上主。这时要是把Server2提升为主,Server3变成Server2的从。这时在Server3上执行change的时候需要做一些计算。如果使用GTID就非常简单, 由于同一事务的GTID在所有节点上的值一致,那么根据Server3当前停止点的GTID就能定位到Server2上的GTID。甚至由于MASTER_AUTO_POSITION功能的出现,都不需要知道GTID的具体值,直接使用CHANGE MASTER TO MASTER_HOST='xxx', MASTER_USER='XXX',MASTER_PASSWORD='XXX',MASTER_AUTO_POSITION=1 ;命令就可以直接完成failover的工作
二、GTID和binlog的关系
1、GTID在binlog中的结构
2、GTID event 结构
- Previous_gtid_log_event
Previous_gtid_log_event 在每个binlog 头部都会有每次binlog rotate的时候存储在binlog头部Previous-GTIDs在binlog中只会存储在这台机器上执行过的所有binlog,不包括手动设置gtid_purged值。换句话说,如果你手动set global gtid_purged=xx; 那么xx是不会记录在Previous_gtid_log_event中的。
- GTID和Binlog之间的关系是怎么对应的呢? 如何才能找到GTID=? 对应的binlog文件呢?
假设有4个binlog: bin.001,bin.002,bin.003,bin.004
bin.001 : Previous-GTIDs=empty; binlog_event有: 1-40
bin.002 : Previous-GTIDs=1-40; binlog_event有: 41-80
bin.003 : Previous-GTIDs=1-80; binlog_event有: 81-120
bin.004 : Previous-GTIDs=1-120; binlog_event有: 121-160
假设现在我们要找GTID=$A,那么MySQL的扫描顺序为:
- 从最后一个binlog开始扫描(即: bin.004)
- bin.004的Previous-GTIDs=1-120,如果$A=140 > Previous-GTIDs,那么肯定在bin.004中
- bin.004的Previous-GTIDs=1-120,如果$A=88 包含在Previous-GTIDs中,那么继续对比上一个binlog文件 bin.003,然后再循环前面2个步骤,直到找到为止.
三、GTID重要参数持久化
1、GTID相关参数
参数 |
commet |
gtid_executed |
执行过的所有GTID |
gtid_purged |
丢弃掉的GTID |
gtid_mode |
gitd模式 |
gtid_next |
session级别的变量,下一个gtid |
gtid_owned |
正在运行的gtid |
enforce_gtid_consistency |
保证GTID安全的参数 |
这里简单说下几个常用参数的作用:
gtid_executed
在当前实例上执行过的 GTID 集合,实际上包含了所有记录到 binlog 中的事务。设置 set sql_log_bin=0 后执行的事务不会生成 binlog 事件,也不会被记录到 gtid_executed 中。执行 RESET MASTER 可以将该变量置空。
gtid_purged
binlog 不可能永远驻留在服务上,需要定期进行清理(通过 expire_logs_days 可以控制定期清理间隔),否则迟早它会把磁盘用尽。gtid_purged 用于记录本机上已经执行过,但是已经被清除了的 binlog 事务集合。它是 gtid_executed 的子集。只有 gtid_executed 为空时才能手动设置该变量,此时会同时更新 gtid_executed 为和 gtid_purged 相同的值。
gtid_executed 为空意味着要么之前没有启动过基于 GTID 的复制,要么执行过 RESET MASTER。执行 RESET MASTER 时同样也会把 gtid_purged 置空,即始终保持 gtid_purged 是 gtid_executed 的子集。
gtid_next
会话级变量,指示如何产生下一个GTID。可能的取值如下:
- AUTOMATIC: 自动生成下一个 GTID,实现上是分配一个当前实例上尚未执行过的序号最小的 GTID。
- ANONYMOUS: 设置后执行事务不会产生GTID。
- 显式指定的GTID: 可以指定任意形式合法的 GTID 值,但不能是当前 gtid_executed 中的已经包含的 GTID,否则下次执行事务时会报错。
2、重要参数如何持久化
1) 如何持久化gtid_executed (前提是log-bin=mysql-bin, log_slave_update=1 )
gtid_executed = mysql.gtid_executed #正常情况下
select * from mysql.gtid_executed; #可查询
或者
gtid_executed = mysql.gtid_executed + last_binlog中最后没写到mysql.gtid_executed中的gtid_event #恢复情况下
2)如何持久化重置的gtid_purged值?
reset master; set global gtid_purged=$A:a-b;
================================================================================================
1. 由于有可能手动设置过gtid_purged=$A:a-b, binlog.index中,last_binlog的Previous-GTIDs并不会包含$A:a-b
2. 由于有可能手动设置过gtid_purged=$A:a-b, binlog.index中,first_binlog的Previous-GTIDs肯定不会出现$A:a-b
3. 重置的gtid_purged = @@global.gtid_executed(mysql.gtid_executed:注意,考虑到这个表的更新触发条件,所以这里
用@@global.gtid_executed代替) - last_binlog的Previous-GTIDs - last_binlog所有的gtid_event
4. 下面就用 $reset_gtid_purged 来表示重置的gtid
3) 如何持久化gtid_purged (前提是log-bin=mysql-bin, log_slave_update=1 )
gtid_purged=binlog.index:first_binlog的Previous-GTIDs + $reset_gtid_purged
四、开启GTID的必要条件
- MySQL 5.6 版本,在my.cnf文件中添加:
gtid_mode=on (必选) #开启gtid功能
log_bin=log-bin=mysql-bin (必选) #开启binlog二进制日志功能
log-slave-updates=1 (必选) #也可以将1写为on
enforce-gtid-consistency=1 (必选) #也可以将1写为on
- MySQL 5.7或更高版本,在my.cnf文件中添加:
gtid_mode=on (必选)
enforce-gtid-consistency=1 (必选)
log_bin=mysql-bin (可选) #高可用切换,最好开启该功能
log-slave-updates=1 (可选) #高可用切换,最好打开该功能
特意说下"log_slave_updates"这个参数选项,通常slave服务器从master服务器接收到的更新不记入slave的二进制日志。该参数选项告诉slave从服务器将其SQL线程执行的更新记入到slave服务器自己的二进制日志。为了使该选项生效,还必须启动binlog二进制日志功能!!比如:
A01和A02为主主复制,A01和B01为主从复制,在测试的过程中发现了以下问题:
- A01和A02的主主复制是没有问题的(从A01写入数据能同步到A02,从A02写入数据能够同步到A01);
- 主从同步的时候,当从A01写入的时候,数据可以写入到B01;
- 当从A02写入的时候,数据就不能写入到B01;
这个问题产生的原因:log_slave_updates参数的状态为NO
五、GTID的工作原理
从服务器连接到主服务器之后,把自己执行过的GTID (Executed_Gtid_Set: 即已经执行的事务编码)<SQL线程> 、获取到的GTID (Retrieved_Gtid_Set: 即从库已经接收到主库的事务编号) <IO线程>发给主服务器,主服务器把从服务器缺少的GTID及对应的transactions发过去补全即可。当主服务器挂掉的时候,找出同步最成功的那台从服务器,直接把它提升为主即可。如果硬要指定某一台不是最新的从服务器提升为主, 先change到同步最成功的那台从服务器, 等把GTID全部补全了,就可以把它提升为主了。
GTID是MySQL 5.6的新特性,可简化MySQL的主从切换以及Failover。GTID用于在binlog中唯一标识一个事务。当事务提交时,MySQL Server在写binlog的时候,会先写一个特殊的Binlog Event,类型为GTID_Event,指定下一个事务的GTID,然后再写事务的Binlog。主从同步时GTID_Event和事务的Binlog都会传递到从库,从库在执行的时候也是用同样的GTID写binlog,这样主从同步以后,就可通过GTID确定从库同步到的位置了。也就是说,无论是级联情况,还是一主多从情况,都可以通过GTID自动找点儿,而无需像之前那样通过File_name和File_position找点儿了。
简而言之,GTID的工作流程为:
- master更新数据时,会在事务前产生GTID,一同记录到binlog日志中。
- slave端的i/o 线程将变更的binlog,写入到本地的relay log中。
- sql线程从relay log中获取GTID,然后对比slave端的binlog是否有记录。
- 如果有记录,说明该GTID的事务已经执行,slave会忽略。
- 如果没有记录,slave就会从relay log中执行该GTID的事务,并记录到binlog。
- 在解析过程中会判断是否有主键,如果没有就用二级索引,如果没有就用全部扫描。
六、GTID的优缺点
GTID的优点
- 一个事务对应一个唯一ID,一个GTID在一个服务器上只会执行一次;
- GTID是用来代替传统复制的方法,GTID复制与普通复制模式的最大不同就是不需要指定二进制文件名和位置;
- 减少手工干预和降低服务故障时间,当主机挂了之后通过软件从众多的备机中提升一台备机为主机;
GTID复制是怎么实现自动同步,自动对应位置的呢?
比如这样一个主从架构:ServerC <-----ServerA ----> ServerB
即一个主数据库ServerA,两个从数据库ServerB和ServerC
当主机ServerA 挂了之后 ,此时ServerB执行完了所有从ServerA 传过来的事务,ServerC 延时一点。这个时候需要把 ServerB 提升为主机 ,Server C 继续为备机;当ServerC 链接ServerB 之后,首先在自己的二进制文件中找到从ServerA 传过来的最新的GTID,然后将这个GTID 发送到ServerB ,ServerB 获得这个GTID之后,就开始从这个GTID的下一个GTID开始发送事务给ServerC。这种自我寻找复制位置的模式减少事务丢失的可能性以及故障恢复的时间。
GTID的缺点(限制)
- 不支持非事务引擎;
- 不支持create table ... select 语句复制(主库直接报错);(原理: 会生成两个sql, 一个是DDL创建表SQL, 一个是insert into 插入数据的sql; 由于DDL会导致自动提交, 所以这个sql至少需要两个GTID, 但是GTID模式下, 只能给这个sql生成一个GTID)
- 不允许一个SQL同时更新一个事务引擎表和非事务引擎表;
- 在一个复制组中,必须要求统一开启GTID或者是关闭GTID;
- 开启GTID后,就不再使用原来的传统复制方式;
- 不支持sql_slave_skip_counter;
七、到底为啥要用GTID?
1. classic replication [传统复制 , 运维之痛]
- GTID replication [GTID复制,很简单]
八、基本运维
创建mysql三个实例server1(3308)、server2(3308)、server3(3308),主从架构:Server2<-----Server1 ----> Server3启动之后,执行change时需要注意
1、搭建GTID,并配置主从关系(server1为主,server2为从)
##### 前提:server1、server2、server3配置文件中关于GTID的配置已完成,mysql服务并启动 ######
登录三台server均执行如下操作:
mysql> set sql_log_bin=0;
mysql> alter user root@'localhost' identified by 'root';
mysql> set sql_log_bin=1;
server1执行:
mysql> set sql_log_bin=0;
mysql> create user rep@'%' identified with mysql_native_password by 'rep';
mysql> grant replication slave on *.* to rep@'%';
mysql> flush privileges;
mysql> set sql_log_bin=1;
server2执行:
mysql> change master to master_host='ip',master_user='rep',master_password='rep',master_port=port,master_auto_position=1;
mysql> start slave;
mysql> show slave status\G;
server3执行:
mysql> change master to master_host='ip',master_user='rep',master_password='rep',master_port=port,master_auto_position=1;
mysql> start slave;
mysql> show slave status\G;
2、测试复制的故障转移
server1(3308)挂了,服务器起不来了。需要把其中的一个从设置为主,另一个设置为其的从库:
server2(3308):
Master_Log_File: mysql-bin3306.000002
Read_Master_Log_Pos: 4156773
Exec_Master_Log_Pos: 4156773
server3(3308):
Master_Log_File: mysql-bin3306.000001
Read_Master_Log_Pos: 83795320
Exec_Master_Log_Pos: 83795320
相比之下server2完成的事务要比server3更接近或则等于server1,现在需要把server3设置为server2的从库。
在MySQL5.6之前,这里的计算会很麻烦,要计算之前主库的log_pos和当前要设置成主库的log_pos,很有可能出错。所以出现了一些高可用性的工具如MHA,MMM等解决问题。
在MySQL5.6之后,很简单的解决了这个难题。因为同一事务的GTID在所有节点上的值一致,那么根据server3当前停止点的GTID就能定位到server2上的GTID,所以直接在server3上执行change即可:
mysql> stop slave;
Query OK, 0 rows affected (0.02 sec)
###千万不要执行 reset master,否则会从最先的GTID上开始执行
mysql> change master to master_host='192.168.221.141',master_user='rep',master_password='rep',master_port=3308,master_auto_position=1; #指定到另一个比较接近主的从上。
Query OK, 0 rows affected, 2 warnings (0.02 sec)
mysql> start slave; #成功的切换到新主
Query OK, 0 rows affected (0.03 sec)
主从结构已经变更,server2是Master,server3是Slave。因为不需要计算pos的值,所以通过GTID很简单的解决了这个问题。
3 跳过复制错误:gtid_next、gtid_purged
1) 从服务器跳过一个错误的事务
mysql> stop slave;
mysql> set session gtid_next='4e659069-3cd8-11e5-9a49-001c4270714e:5'; #在session里设置gtid_next,即跳过这个GTID
mysql> begin; #开启一个空事务
mysql> commit;
mysql> SET SESSION GTID_NEXT = AUTOMATIC; #把gtid_next设置回来
mysql> start slave; #开启复制
在此成功跳过了错误,同步继续。可以通过这个办法来处理复制失败的问题,这里还有个例子(从服务器中跳过一条语句/事务),比如主库无意间删除了某张表,利用重复延迟复制保留数据:
######### server2 从库操作 ##############
mysql > stop slave;
Query OK, 0 ROWS affected (0.05 sec)
mysql > CHANGE master TO MASTER_DELAY=600; ##设置主从延迟同步的时间(是延迟加载relay_log),单位为秒
Query OK, 0 ROWS affected (0.27 sec)
mysql > START slave;
Query OK, 0 ROWS affected, 1 warning (0.06 sec)
####### server1 master 原本是正常的, 然后意外地执行了 truncate table: #######
####### 主库操作 #####################
mysql > INSERT INTO t SET title='c';
Query OK, 1 ROW affected (0.03 sec)
mysql > INSERT INTO t SET title='d';
Query OK, 1 ROW affected (0.05 sec)
mysql > SHOW master STATUS \G
*************************** 1. ROW ***************************
File: black-bin.000001
POSITION: 2817
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: 0c005b76-d3c7-11e2-a27d-274c063b18c4:1-10
1 ROW IN SET (0.00 sec)
mysql > TRUNCATE TABLE t;
Query OK, 0 ROWS affected (0.15 sec)
mysql > SHOW master STATUS \G
*************************** 1. ROW ***************************
File: black-bin.000001
POSITION: 2948
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: 0c005b76-d3c7-11e2-a27d-274c063b18c4:1-11
1 ROW IN SET (0.00 sec)
slave有延迟, 虽然已经获取到了gtid及对应的events, 但是并未执行:
mysql > SHOW slave STATUS \G
*************************** 1. ROW ***************************
Slave_IO_State: Waiting FOR master TO send event
.......
.......
SQL_Delay: 600
SQL_Remaining_Delay: 565
Slave_SQL_Running_State: Waiting until MASTER_DELAY seconds after master executed event
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: 0c005b76-d3c7-11e2-a27d-274c063b18c4:9-11
Executed_Gtid_Set: 0c005b76-d3c7-11e2-a27d-274c063b18c4:1-8
Auto_Position: 1
1 ROW IN SET (0.00 sec)
要想办法在slave中跳过 GTID:0c005b76-d3c7-11e2-a27d-274c063b18c4:11, 也就是那条truncate table语句 。
办法就是设置GTID_NEXT,然后提交一个空的事务。
mysql > stop slave;
Query OK, 0 ROWS affected (0.03 sec)
mysql > SET session gtid_next='0c005b76-d3c7-11e2-a27d-274c063b18c4:11';
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.01 sec)
mysql >SET SESSION GTID_NEXT = AUTOMATIC;
Query OK, 0 ROWS affected (0.00 sec)
mysql > START slave;
Query OK, 0 ROWS affected, 1 warning (0.07 sec)
查看复制状态
mysql > SHOW slave STATUS \G
*************************** 1. ROW ***************************
Slave_IO_State: Waiting FOR master TO send event
.......
.......
Retrieved_Gtid_Set: 0c005b76-d3c7-11e2-a27d-274c063b18c4:9-11
Executed_Gtid_Set: 0c005b76-d3c7-11e2-a27d-274c063b18c4:1-11
Auto_Position: 1
1 ROW IN SET (0.00 sec)
mysql > SELECT * FROM t;
+----+-------+
| id | title |
+----+-------+
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |
+----+-------+
4 ROWS IN SET (0.00 sec)
成功跳过 truncate table, 当然此时主从的数据已经不一致了。
注意:通过GTID的复制都是没有指定MASTER_LOG_FILE和MASTER_LOG_POS的,所以通过GTID复制都是从最先开始的事务开始,除非在自己的binlog里面有执行过之前的记录,才会继续后面的执行
2)通过server2从库去恢复server3从库(在上面的基础之上进行操作)
##### server3 上查看t2表 #############3
mysql> select * from t2;
Empty set (0.00 sec)
因为server1执行了truncate t2表,server3复制server1的操作
mysql> stop slave; ##数据丢失,停止复制
mysql> reset master; ##在还原数据之前,一定要清空GTID
########## server2上进行数据备份 #########
[root@db141 ~]# mysqldump -uroot -proot -S data3308/my3308.sock --single-transaction --master-data=2 --set-gtid-purged=ON -A>all.sql;
[root@db141 ~]# vim all.sql
SET @MYSQLDUMP_TEMP_LOG_BIN = @@SESSION.SQL_LOG_BIN;
SET @@SESSION.SQL_LOG_BIN= 0;
--
-- GTID state at the beginning of the backup
--
SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-15,
c26609a0-c442-11eb-babf-000c29d84243:1';
注意:在备份的时候设置了--set-gtid-purged=ON(默认就是ON),则在数据恢复的时候,即 server3是不会写入bin-log日志的。但是会执行gtid语句,而且这些gtid语句的uuid是备份这数据数据库的uuid;
##### server3恢复数据#####
mysql> source /root/all.sql;
mysql> show master status;
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------+
| mysql_bin.000001 | 152 | | | 09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-15,
c26609a0-c442-11eb-babf-000c29d84243:1 |
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> start slave;
如果server2在数据备份的时候,命令如下:
[root@db141 ~]# mysqldump -uroot -proot -S data3308/my3308.sock --single-transaction --master-data=2 --set-gtid-purged=OFF -A>all.sql;
设置了--set-gtid-purged=OFF,则在数据恢复的时候,即 server3是会写入到binlog日志中的,执行的gtid语句的uuid则是server3自己的uuid;
mysql> source /root/all.sql;
mysql> show master status;
+------------------+----------+--------------+------------------+--------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+--------------------------------------------+
| mysql_bin.000001 | 1191825 | | | adf717ab-c4d4-11eb-ae0a-000c29ba607f:1-149 |
+------------------+----------+--------------+------------------+--------------------------------------------+
1 row in set (0.00 sec)
mysql> select @@server_uuid;
+--------------------------------------+
| @@server_uuid |
+--------------------------------------+
| adf717ab-c4d4-11eb-ae0a-000c29ba607f |
+--------------------------------------+
3) 事务日志被purge ,再change
server1上有一批操作,并有日志被purge,server2再去change,注意此时server2的gtid_executed为空
###### server1 操作##################
mysql> show binary logs;
+------------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+------------------+-----------+-----------+
| mysql_bin.000001 | 156 | No |
+------------------+-----------+-----------+
1 row in set (0.00 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql_bin.000001 | 156 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
mysql> select * from t2;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.01 sec)
mysql> insert into t2 values(2);
Query OK, 1 row affected (0.00 sec)
mysql> insert into t2 values(3);
Query OK, 1 row affected (0.00 sec)
mysql> show binary logs;
+------------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+------------------+-----------+-----------+
| mysql_bin.000001 | 710 | No |
+------------------+-----------+-----------+
1 row in set (0.00 sec)
mysql> flush logs;
Query OK, 0 rows affected (0.03 sec)
mysql> insert into t2 values(4);
Query OK, 1 row affected (0.00 sec)
mysql> insert into t2 values(5);
Query OK, 1 row affected (0.01 sec)
mysql> insert into t2 values(6);
Query OK, 1 row affected (0.01 sec)
mysql> flush logs;
Query OK, 0 rows affected (0.03 sec)
mysql> insert into t2 values(7);
Query OK, 1 row affected (0.00 sec)
mysql> insert into t2 values(8);
Query OK, 1 row affected (0.00 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+------------------------------------------+
| mysql_bin.000003 | 750 | | | 09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-7 |
+------------------+----------+--------------+------------------+------------------------------------------+
1 row in set (0.00 sec)
mysql> show binary logs;
+------------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+------------------+-----------+-----------+
| mysql_bin.000001 | 757 | No |
| mysql_bin.000002 | 1074 | No |
| mysql_bin.000003 | 750 | No |
+------------------+-----------+-----------+
mysql> show variables like '%gtid%';
+----------------------------------+------------------------------------------+
| Variable_name | Value |
+----------------------------------+------------------------------------------+
| binlog_gtid_simple_recovery | ON |
| enforce_gtid_consistency | ON |
| gtid_executed | 09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-7 |
| gtid_executed_compression_period | 0 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | |
| session_track_gtids | OFF |
+----------------------------------+------------------------------------------+
mysql> purge binary logs to 'mysql_bin.000003'; #日志被purge之后剩下的binlog
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like '%gtid%';
+----------------------------------+------------------------------------------+
| Variable_name | Value |
+----------------------------------+------------------------------------------+
| binlog_gtid_simple_recovery | ON |
| enforce_gtid_consistency | ON |
| gtid_executed | 09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-7 |
| gtid_executed_compression_period | 0 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | 09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-5 |
| session_track_gtids | OFF |
+----------------------------------+------------------------------------------+
9 rows in set (0.00 sec)
mysql> show binary logs;
+------------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+------------------+-----------+-----------+
| mysql_bin.000003 | 750 | No |
+------------------+-----------+-----------+
1 row in set (0.00 sec)
###### server2 change操作################
mysql> change master to master_host='192.168.221.140',master_user='rep',master_password='rep',master_port=3308,master_auto_position=1;
Query OK, 0 rows affected, 8 warnings (0.03 sec)
mysql> start slave;
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State:
Master_Host: 192.168.221.140
Master_User: rep
Master_Port: 3308
Connect_Retry: 60
Master_Log_File:
Read_Master_Log_Pos: 4
Relay_Log_File: relay_bin.000001
Relay_Log_Pos: 4
Relay_Master_Log_File:
Slave_IO_Running: No
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
.........................
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 13114
Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Cannot replicate because the master purged required binary logs. Replicate the missing transactions from elsewhere, or provision a new slave from backup. Consider increasing the master's binary log expiration period. The GTID set sent by the slave is '', and the missing transactions are '09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-5''
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
-----------------------------------------
mysql> stop slave;
mysql> set global gtid_purged ='09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-5'; #只有gtid_executed为空的时候,才能手动设置gtid_purged
mysql> start slave;
mysql> show variables like '%gtid%';
+----------------------------------+------------------------------------------+
| Variable_name | Value |
+----------------------------------+------------------------------------------+
| binlog_gtid_simple_recovery | ON |
| enforce_gtid_consistency | ON |
| gtid_executed | 09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-5 |
| gtid_executed_compression_period | 0 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | 09d4c484-afc6-11eb-a6fd-000c293bb7d1:1-5 |
| session_track_gtids | OFF |
+----------------------------------+------------------------------------------+
9 rows in set (0.00 sec)
mysql> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.221.140
Master_User: rep
Master_Port: 3308
Connect_Retry: 60
Master_Log_File: mysql_bin.000003
Read_Master_Log_Pos: 750
Relay_Log_File: relay_bin.000002
Relay_Log_Pos: 363
Relay_Master_Log_File: mysql_bin.000003
Slave_IO_Running: Yes
Slave_SQL_Running: No
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 1049
Last_Error: Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 1 failed executing transaction '09d4c484-afc6-11eb-a6fd-000c293bb7d1:6' at master log mysql_bin.000003, end_log_pos 442. See error log and/or performance_schema.replication_applier_status_by_worker table for more details about this failure or others, if any.
4 如何从classic replication 升级为GTID replication
- offline 方式升级 (线下升级)
offline 的方式升级最简单:
- 全部关闭mysql
- 在my.cnf文件中配置好GTID
- 重启mysql
- 登录mysql,执行"change master to MASTER_AUTO_POSITION=1;"
- online 方式升级 (线上升级)
先介绍几个重要GTID_MODE的参数:
GTID_MODE = OFF
不产生Normal_GTID,只接受来自master的ANONYMOUS_GTID
GTID_MODE = OFF_PERMISSIVE
不产生Normal_GTID,可以接受来自master的ANONYMOUS_GTID & Normal_GTID
GTID_MODE = ON_PERMISSIVE
产生Normal_GTID,可以接受来自master的ANONYMOUS_GTID & Normal_GTID
GTID_MODE = ON
产生Normal_GTID,只接受来自master的Normal_GTID
归纳总结:
1)当master产生Normal_GTID的时候,如果slave的gtid_mode(OFF)不能接受Normal_GTID,那么就会报错
2)当master产生ANONYMOUS_GTID的时候,如果slave的gtid_mode(ON)不能接受ANONYMOUS_GTID,那么就会报错
3)设置auto_position的条件: 当master的gtid_mode=ON时,slave可以为OFF_PERMISSIVE,ON_PERMISSIVE,ON。
除此之外,都不能设置auto_position = on
============================================
下面开始说下如何online 升级为GTID模式?
step 1: 每台server执行
检查错误日志,直到没有错误出现,才能进行下一步
mysql> SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = WARN;
step 2: 每台server执行
mysql> SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = ON;
step 3: 每台server执行
不用关心一组复制集群的server的执行顺序,只需要保证每个Server都执行了,才能进行下一步
mysql> SET @@GLOBAL.GTID_MODE = OFF_PERMISSIVE;
step 4: 每台server执行
不用关心一组复制集群的server的执行顺序,只需要保证每个Server都执行了,才能进行下一步
mysql> SET @@GLOBAL.GTID_MODE = ON_PERMISSIVE;
step 5: 在每台server上执行,如果ONGOING_ANONYMOUS_TRANSACTION_COUNT=0就可以
不需要一直为0,只要出现过0一次,就ok
mysql> SHOW STATUS LIKE 'ONGOING_ANONYMOUS_TRANSACTION_COUNT';
step 6: 确保所有anonymous事务传递到slave上了
#master上执行
mysql> SHOW MASTER STATUS;
#每个slave上执行
mysql> SELECT MASTER_POS_WAIT(file, position);
或者,等一段时间,只要不是大的延迟,一般都没问题
step 7: 每台Server上执行
mysql> SET @@GLOBAL.GTID_MODE = ON;
step 8: 在每台server上将my.cnf中添加好gtid配置
gtid_mode=on
enforce-gtid-consistency=1
log_bin=mysql-bin
log-slave-updates=1
step 9: 在从机上通过change master语句进行复制
mysql> STOP SLAVE;
mysql> CHANGE MASTER TO MASTER_AUTO_POSITION = 1;
mysql> START SLAVE;
-----------------------------------------------------------------------------------------------------------------------------------------
前提:
```
基于binlog日志已搭建好主从关系,29:3306和29:3307互为主从关系
```
1、从传统复制模式切换为GTID复制模式
```bash
# 在主从库上同时修改参数enforce_gtid_consistency=warn,确保error log中不会出现警告信息,如# # 果有,需要先修复,再继续
mysql> set global enforce_gtid_consistency=warn;
# 在主从服务器上把enforce_gtid_consistency修改为on,保障GTID的一致性
mysql> set global enforce_gtid_consistency=on;
# 在主从服务器上同时调整GTID模式为off_permissive
mysql> set global gtid_mode=off_permissive;
# 在主从服务器上同时调整GTID模式为on_permissive
mysql> set global gtid_mode=on_permissive;
# 确认冲库的ongoing_anonymous_transaction_count的参数是否为0,如果为0,意味着没有等待的事务,可以直接进行下一步
mysql> show global status like 'ongoing_anonymous_%';
+-------------------------------------+-------+
| Variable_name | Value |
+-------------------------------------+-------+
| Ongoing_anonymous_transaction_count | 0 |
+-------------------------------------+-------+
1 row in set (0.00 sec)
# 在主从库上同时设置gtid_mode为开启状态
mysql> set global gtid_mode=on;
mysql> show variables like '%gtid%';
+----------------------------------+-----------+
| Variable_name | Value |
+----------------------------------+-----------+
| binlog_gtid_simple_recovery | ON |
| enforce_gtid_consistency | ON |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | |
| session_track_gtids | OFF |
+----------------------------------+-----------+
# 把传统复制模式改为GTID复制,先把原有的传统复制停掉stop slave,在执行change master to master_auto_position=1,再启动slave
# 更改29:3307的配置
mysql> stop slave;
mysql> change master to master_auto_position=1;
mysql> start slave;
# 测试在主库29:3306添加一条数据
mysql> insert into test04.t4 values(1,'tt','cd');
# 在从库29:3307验证
mysql> show slave status\G;
Retrieved_Gtid_Set: 22346d12-9a6a-11ea-bd3c-fa163e25e077:1
Executed_Gtid_Set: 22346d12-9a6a-11ea-bd3c-fa163e25e077:1
Auto_Position: 1
# 从传统的binlog日志复制转变为gtid复制模式成功
# 更改29:3306的配置
mysql> stop slave;
mysql> change master to master_auto_position=1;
mysql> start slave;
ERROR 1872 (HY000): Slave failed to initialize relay log info structure from the repository
# 报错原因启动slave时,使用repository中信息初始化relay log结构失败了,解决方法如下:
mysql> reset slave;
mysql> start slave;
# 测试在29:3307添加一条数据
mysql> insert into test04.t4 values(5,'uu','ff');
# 在29:3306上验证
mysql> show slave status\G;
Retrieved_Gtid_Set: 5fcfeb62-9b29-11ea-8018-fa163e25e077:1
Executed_Gtid_Set: 22346d12-9a6a-11ea-bd3c-fa163e25e077:1,
5fcfeb62-9b29-11ea-8018-fa163e25e077:1
```
2、从GTID复制模式切换为传统复制模式的过程
```bash
# 思路:先在从库中执行stop slave,停掉主从复制,然后调整为传统复制模式,让master_auto_position=0
# 在29:3307和29:3306同时执行
mysql> stop slave;
Query OK, 0 rows affected (0.00 sec)
# 29:3306上执行
mysql> change master to master_auto_position=0,master_host="192.168.1.29", master_port=3307,master_user="yaya",master_password="123456",\
-> master_log_file="mysql-bin.000037", master_log_pos=680;
Query OK, 0 rows affected, 2 warnings (0.07 sec)
# 29:3307上执行
mysql> change master to master_auto_position=0,master_host="192.168.1.29", master_port=3306, master_user="yaya",master_password="123456", \
-> master_log_file="mysql-bin.000051", master_log_pos=680;
# 3306和3307开启复制功能
mysql> start slave;
Query OK, 0 rows affected (0.08 sec)
# 3306和3307服务器上同时调整GTID模式为
mysql> set global gtid_mode=on_permissive;
# 3306和3307服务器上同时调整GTID模式
mysql> set global gtid_mode=off_permissive;
# 3306和3307服务器上同时关闭GTID功能
mysql> set global enforce_gtid_consistency=off;
mysql> set global gtid_mode=off;
注意: 如果在配置文件中配置过gtid_mode和enforce_gtid_consistency两个参数,记得更改配置文件,下次重启服务后直接生效
# 测试,分别在3306和3307上分别插入数据,查看是否同步成功,并用show slave status\G;查看以下参数是否发生改变
Retrieved_Gtid_Set:
Executed_Gtid_Set: 22346d12-9a6a-11ea-bd3c-fa163e25e077:1-2,
5fcfeb62-9b29-11ea-8018-fa163e25e077:1-2
Auto_Position: 0
# gtid的值未增加,并且Auto_Position为0,说明切换成功