对于2台主以上的结构优势异常明显,可以在数据不丢失的情况下切换新主。
通过GTID复制,这些在主从成立之前的操作也会被复制到从服务器上,引起复制失败。也就是说通过GTID复制都是从最先开始的事务日志开始,即使这些操作在复制之前执行。比如在server1上执行一些drop、delete的清理操作,接着在server2上执行change的操作,会使得server2也进行server1的清理操作。
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 (可选) #高可用切换,最好打开该功能
GTID实际上是由UUID+TID (即transactionId)组成的。其中UUID(即server_uuid) 产生于auto.conf文件(cat /data/mysql/data/auto.cnf),是一个MySQL实例的唯一标识。TID代表了该实例上已经提交的事务数量,并且随着事务提交单调递增,所以GTID能够保证每个MySQL实例事务的执行(不会重复执行同一个事务,并且会补全没有执行的事务)。GTID在一组复制中,全局唯一。
直接使用CHANGE MASTER TO MASTER_HOST='xxx', MASTER_AUTO_POSITION命令就可以直接完成failover的工作。
GTID的工作原理
从服务器连接到主服务器之后,把自己执行过的GTID (Executed_Gtid_Set: 即已经执行的事务编码)<SQL线程> 、获取到的GTID (Retrieved_Gtid_Set: 即从库已经接收到主库的事务编号) <IO线程>发给主服务器,主服务器把从服务器缺少的GTID及对应的transactions发过去补全即可。当主服务器挂掉的时候,找出同步最成功的那台从服务器,直接把它提升为主即可。如果硬要指定某一台不是最新的从服务器提升为主, 先change到同步最成功的那台从服务器, 等把GTID全部补全了,就可以把它提升为主了。
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需要重启 (mysql5.7除外);
- 开启GTID后,就不再使用原来的传统复制方式;
- 对于create temporary table 和 drop temporary table语句不支持;
- 不支持sql_slave_skip_counter;
开启GTID以后,无法使用sql_slave_skip_counter跳过事务,因为主库会把从库缺失的GTID,发送给从库,所以skip是没有用的。
为了提前发现问题,在gtid模式下,直接禁止使用set global sql_slave_skip_counter =x。
正确的做法: 通过set gtid_next= 'aaaa'('aaaa'为待跳过的事务),然后执行BIGIN; 接着COMMIT产生一个空事务,占据这个GTID,再START SLAVE,会发现下一条事务的GTID已经执行过,就会跳过这个事务了。如果一个GTID已经执行过,再遇到重复的GTID,从库会直接跳过,可看作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,否则下次执行事务时会报错。
跳过复制错误:gtid_next、gtid_purged
因为是通过GTID来进行复制的,也需要跳过这个事务从而继续复制,这个事务可以到主上的binlog里面查看:因为不知道找哪个GTID上出错,所以也不知道如何跳过哪个GTID
1、show slave status里的信息里可以找到在执行Master里的POS:151
2、通过mysqlbinlog找到了GTID:
3、stop slave;
4、set session gtid_next='4e659069-3cd8-11e5-9a49-001c4270714e:1'
5、begin; commit;
6、SET SESSION GTID_NEXT = AUTOMATIC; #把gtid_next设置回来
7、start slave; #开启复制
Slave如何跳过purge的部分,而不是在最先开始的事务执行。
在主上执行,查看被purge的GTID:
mysql> show global variables like 'gtid_purged';
±--------------±------------------------------------------+
| Variable_name | Value |
±--------------±------------------------------------------+
| gtid_purged | 4e659069-3cd8-11e5-9a49-001c4270714e:1-50 |
±--------------±------------------------------------------+
1 row in set (0.00 sec)
在从上执行,跳过这个GTID:
mysql> stop slave;
Query OK, 0 rows affected (0.00 sec)
mysql> set global gtid_purged = '4e659069-3cd8-11e5-9a49-001c4270714e:1-50';
Query OK, 0 rows affected (0.02 sec)
mysql> reset master;
Query OK, 0 rows affected (0.04 sec)
mysql> start slave;
Query OK, 0 rows affected (0.01 sec)
要是出现:
ERROR 1840 (HY000): @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty.
则需要执行:
mysql> reset master;
GTID跳过复制错误的方法
1)对于跳过一个错误,找到无法执行事务的编号,比如是2a09ee6e-645d-11e7-a96c-000c2953a1cb:1-10
mysql> stop slave;
mysql> set gtid_next='2a09ee6e-645d-11e7-a96c-000c2953a1cb:1-10';
mysql> begin;
mysql> commit;
mysql> set gtid_next='AUTOMATIC';
mysql> start slave;
2)上面方法只能跳过一个事务,那么对于一批如何跳过?
在主库执行"show master status",看主库执行到了哪里,比如:2a09ee6e-645d-11e7-a96c-000c2953a1cb:1-33,那么操作如下:
mysql> stop slave;
mysql> reset master;
mysql> set global gtid_purged='2a09ee6e-645d-11e7-a96c-000c2953a1cb:1-33';
mysql> start slave;
如何从classic replication 升级成 GTID replication
先介绍几个重要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;
如何从classic replication 升级成 GTID replication
1、GTID切换为传统复制
从库上
mysql> stop slave;
Query OK, 0 rows affected (0.01 sec)
mysql> change master to master_auto_position=0,master_host='39.108.108.46',master_user='cjr',master_password='cjr', master_log_file='mysql-bin.000001',master_log_pos=416,MASTER_PORT=3306;
Query OK, 0 rows affected, 2 warnings (0.02 sec)
start slave
主从同时
mysql> set global gtid_mode=on_permissive;
Query OK, 0 rows affected (0.02 sec)
mysql> set global gtid_mode=off_permissive;
Query OK, 0 rows affected (0.01 sec)
mysql> set global enforce_gtid_consistency=off;
Query OK, 0 rows affected (0.00 sec)
mysql> set global gtid_mode=off;
Query OK, 0 rows affected (0.02 sec)
1、GTID切换为传统复制
主从库上同时执行
mysql> set global enforce_gtid_consistency=warn;
Query OK, 0 rows affected (0.00 sec)
mysql> set global enforce_gtid_consistency=on;
Query OK, 0 rows affected (0.00 sec)
mysql> set global gtid_mode=off_permissive;
Query OK, 0 rows affected (0.01 sec)
mysql> set global gtid_mode=on_permissive;
Query OK, 0 rows affected (0.01 sec)
从库上确保 参数是否是0,表示没有等待的事务
mysql> show global status like 'ongoing_anonymous_%';
±------------------------------------±------+
| Variable_name | Value |
±------------------------------------±------+
| Ongoing_anonymous_transaction_count | 0 |
±------------------------------------±------+
1 row in set (0.01 sec)
主库同时
mysql> set global gtid_mode=on;
Query OK, 0 rows affected (0.01 sec)
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 |
±---------------------------------
从库上
mysql> stop slave;
Query OK, 0 rows affected (0.01 sec)
mysql> change master to master_host='39.108.108.46',master_port=3306,master_user='cjr',master_password='cjr',master_auto_position=1;
Query OK, 0 rows affected, 2 warnings (0.02 sec)
mysql> start slave;
Query OK, 0 rows affected (0.05 sec)