一、复制新特性*(GTID)介绍
MySQL 5.6使用server_uuid和transaction_id两个共同组成一个GTID。即:GTID = server_uuid:transaction_id
它可简化MySQL的主从切换以及Failover。GTID用于在binlog中唯一标识一个事务。当事务提交时,MySQL Server在写binlog的时候,会先写一个特殊的Binlog Event,类型为GTID_Event,指定下一个事务的GTID,然后再写事务的Binlog。主从同步时GTID_Event和事务的Binlog都会传递到从库,从库在执行的时候也是用同样的GTID写binlog,这样主从同步以后,就可通过GTID确定从库同步到的位置了。也就是说,无论是级联情况,还是一主多从情况,都可以通过GTID自动找点儿,而无需像之前那样通过File_name和File_position找点儿了。
server_uuid是MySQL Server的只读变量,保存在数据目录下的auto.cnf中,可直接通过cat命令查看。MySQL第一次启动时候创建auto.cnf文件,并生成server_uuid(MySQL使用机器网卡,当前时间,随机数等拼接成一个128bit的uuid,可认为在全宇宙都是唯一的,在未来一百年,使用同样的算法生成的uuid是不会冲突的)。之后MySQL再启动时不会重复生成uuid,而是使用auto.cnf中的uuid。也可以通过MySQL客户端使用如下命令查看server_uuid,看到的实际上是server_uuid的十六进制编码,总共16字节(其中uuid中的横线只是为了便于查看,并没有实际意义)。
mysql> show global variables like 'server_uuid';
+---------------+--------------------------------------+
| Variable_name | Value |
+---------------+--------------------------------------+
| server_uuid | b3485508-883f-11e5-85fb-e41f136aba3e |
+---------------+--------------------------------------+
1 row in set (0.00 sec)
在同一个集群内,每个MySQL实例的server_uuid必须唯一,否则同步时,会造成IO线程不停的中断,重连。在通过备份恢复数据时,一定要将var目录中的auto.cnf删掉,让MySQL启动时自己生成uuid。
GTID中还有一部分是transaction_id,同一个server_uuid下的transaction_id一般是递增的。如果一个事务是通过用户线程执行,那么MySQL在生成的GTID时,会使用它自己的server_uuid,然后再递增一个transaction_id作为该事务的GTID。当然,如果事务是通过SQL线程回放relay-log时产生,那么GTID就直接使用binlog里的了。在MySQL 5.6中不用担心binlog里没有GTID,因为如果从库开启了GTID模式,主库也必须开启,否则IO线程在建立连接的时候就中断了。5.6的GTID对MySQL的集群环境要求是非常严格的,要么主从全部开启GTID模式,要么全部关闭GTID模式。
刚才提到,同一个server_uuid下的transaction_id一般是递增的,难道在某些情况下不是递增的吗?答案是肯定的。MySQL支持通过设置Session级别的变量gtid_next,来指定下一个事务的GTID,格式就是‘server_uuid:transaction_id'。之后还可以改回AUTOMATIC(默认值)
mysql> set gtid_next = 'b694c8b2-883f-11e5-85fb-e41f136aba3e:12000005';
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> set gtid_next = AUTOMATIC;
Query OK, 0 rows affected (0.00 sec)
一般设置gtid_next是加1,用于主从同步时跳过一个事务。但是如果设置gtid_next之后,导致当前server_uuid下的transaction_id不连续,那么坑爹的地方也就出现了。在改回AUTOMATIC以后,再有事务执行时,MySQL生成transaction_id时,不是按当前最大的transaction_id继续增长,而是补缺口(使用最小的缺失的那个transaction_id作为下一个gtid)。这样的话,即使是同一个server_uuid,也不能通过transaction_id的大小来判断事务的顺序。
使用server_uuid:transaction_id共同组成一个GTID的好处是,由于server_uuid唯一,即使一个集群内多个节点同时有写入,也不会造成GTID冲突。
GTID的使用
MySQL通过全局变量gtid_mode控制开启/关闭GTID模式。但是gtid_mode是只读的,可添加到配置文件中,然后重启mysqld来开启GTID模式。相关配置项如下:
gtid-mode = ON
enforce_gtid_consistency = 1
log-slave-updates = 1
log-bin = mysql-bin
log-bin-index = mysql-bin.index
配置方式为gtid_mode=ON/OFF。让人诧异的是gtid_mode的类型为枚举类型,枚举值可以为ON和OFF,所以应该通过ON或者OFF来控制gtid_mode,不要把它配置成0或者1,否则结果可能不符合你的预期。开启gtid_mode时,log-bin和log-slave-updates也必须开启,否则MySQL Server拒绝启动。除此以外,enforce-gtid-consistency也必须开启,否则MySQL Server也拒绝启动。enforce-gtid-consistency是因为开启grid_mode以后,许多MySQL的SQL和GTID是不兼容的。比如开启ROW 格式时,CREATE TABLE ... SELECT,在binlog中会形成2个不同的事务,GTID无法唯一。另外在事务中更新MyISAM表也是不允许的。
刚才已经提到,当开启GTID模式时,集群中的全部MySQL Server必须同时配置gtid_mod = ON,否则无法同步。
一旦使用GTID模式同步以后,主从切换就可以使用GTID来自动找点儿了,使用方式是在CHANGE MASTER时指定MASTER_AUTO_POSITION=1。命令如下:
mysql> CHANGE MASTER TO
-> MASTER_HOST = '',
-> MASTER_PORT = 3306,
-> MASTER_USER = 'test',
-> MASTER_PASSWORD = '',
-> MASTER_AUTO_POSITION = 1;
mysql> show global variables like 'gtid_%';
+---------------+----------------------------------------------------------------------------------------------+
| Variable_name | Value |
+---------------+----------------------------------------------------------------------------------------------+
| gtid_executed | b694c8b2-883f-11e5-85fb-e41f136aba3e:1-10114525:12000000-12000005 |
| gtid_mode | ON |
| gtid_owned | b694c8b2-883f-11e5-85fb-e41f136aba3e:10114523#10:10114525#6:10114521#5:10114524#8:10114522#4 |
| gtid_purged | b694c8b2-883f-11e5-85fb-e41f136aba3e:1-8993295 |
+---------------+-------------------------------------------------------------------------------------------
这里有4个变量,其中gtid_mode已经介绍过了,其他3个变量的含义如下:
gtid_executed:这既是一个Global级别的变量,又是一个Session级别的变量,是只读变量。Global级别的gtid_executed表示当前实例已经执行过的GTID集合。
Session级别的gtid_executed一般情况下是空的。
gtid_owned:这既是一个Global级别的变量,又是一个Session级别的变量,是只读变量。Global级别的gtid_owned表示当前实例正在执行中的GTID,以及对应的线程id。Session级别的gtid_owned一般情况下是空的。
gtid_purged:这是一个Global级别的变量,可动态修改。我们知道binlog可以被purge掉,gtid_purged表示当前实例中已经被purge掉的GTID集合,很明显gtid_purged是gtid_executed的子集。但是gtid_purged也不是可以随意修改的,必须在@@global.gtid_executed是空的情况下,才可以动态设置gtid_purged。
GTID相关Binlog
通过前面的介绍可以知道,GTID可以在binlog中唯一标识一个事务,要了解GTID找点儿原理,就必须知道Binlog的格式,首先看一段Binlog
# at 120
#151222 9:07:58 server id 1026872634 end_log_pos 247 CRC32 0xedf993a8 Previous-GTIDs
# b3485508-883f-11e5-85fb-e41f136aba3e:1-14,
# b694c8b2-883f-11e5-85fb-e41f136aba3e:1-10115960:12000000-12000005
# at 247
#151222 9:08:03 server id 1026872625 end_log_pos 295 CRC32 0xc3d3d8ee GTID [commit=yes]
SET @@SESSION.GTID_NEXT= 'b694c8b2-883f-11e5-85fb-e41f136aba3e:10115961'/*!*/;
# at 295
#151222 9:08:03 server id 1026872625 end_log_pos 370 CRC32 0x0a32d229 Query thread_id=18 exec_time=1 error_code=0
BEGIN
/*!*/;
# at 370
#151222 9:08:03 server id 1026872625 end_log_pos 480 CRC32 0x3c0e094f Query thread_id=18 exec_time=1 error_code=0
use `db`/*!*/;
SET TIMESTAMP=1450746483/*!*/;
update tb set val = val + 1 where id = 1
/*!*/;
# at 480
#151222 9:08:03 server id 1026872625 end_log_pos 511 CRC32 0x5772f16b Xid = 6813913
COMMIT/*!*/;
# at 511
#151222 9:10:19 server id 1026872625 end_log_pos 559 CRC32 0x3ac30191 GTID [commit=yes]
SET @@SESSION.GTID_NEXT= 'b694c8b2-883f-11e5-85fb-e41f136aba3e:10115962'/*!*/;
# at 559
#151222 9:10:19 server id 1026872625 end_log_pos 634 CRC32 0x83a74912 Query thread_id=18 exec_time=0 error_code=0
SET TIMESTAMP=1450746619/*!*/;
BEGIN
/*!*/;
# at 634
#151222 9:10:19 server id 1026872625 end_log_pos 744 CRC32 0x581f6031 Query thread_id=18 exec_time=0 error_code=0
SET TIMESTAMP=1450746619/*!*/;
update tb set val = val + 1 where id = 1
/*!*/;
# at 744
#151222 9:10:19 server id 1026872625 end_log_pos 775 CRC32 0x793f8e34 Xid = 6813916
COMMIT/*!*/;
这段Binlog从文件120偏移处(Format_description_log_event之后的第一个Binlog Event)开始截取。可以看到,第一个Binlog Event的类型为:Previous-GTIDs,它存在于每个binlog文件中。当开启GTID时,每个binlog文件都有且只有一个Previous-GTIDs,位置都是在Format_description_log_event之后的第一个Binlog Event处。它的含义是在当前Binlog文件之前执行过的GTID集合,可以充当索引用,使用这个Binlog Event,可以便于快速判断GTID是否位于当前binlog文件中。
下面看看gtid_purged和gtid_executed是如何构造的。MySQL在启动时打开最老的binlog文件,读取其中的Previous-GTIDs,那么就是@@global.gtid_purged。MySQL在启动时打开最新的binlog文件,读取其中的Previous-GTIDs,构造一个gtid_set,然后再遍历这个最新的binlog文件,把遇到的每个gtid都添加到gtid_set中,当文件遍历完成时,这个gtid_set就是@@global.gtid_executed。
前面说过只有在@@global.gtid_executed为空的情况下,才可以动态设置@@global.gtid_purged。因此可以通过RESET MASTER的方式来清空@@global.gtid_executed。这一点,类似Ares中的命令:set binlog_group_id=XXX, master_server_id=YYY with reset;(是会删除binlog的)
通过解析上面的binlog文件,我们也可以看到,每个事务之前,都有一个GTID_log_event,用来指定GTID的值。总体来看,一个MySQL binlog的格式大致如下:
GTID找点儿原理
我们知道,在未开启GTID模式的情况下,从库用(File_name和File_pos)二元组标识执行到的位置。START SLAVE时,从库会先向主库发送一个BINLOG_DUMP命令,在BINLOG_DUMP命令中指定File_name和File_pos,主库就从这个位置开始发送binlog。
在开启GTID模式的情况下,如果指定MASTER_AUTO_POSITION=1。START SLAVE时,从库会计算Retrieved_Gtid_Set和Executed_Gtid_Set的并集(通过SHOW SLAVE STATUS可以查看),然后把这个GTID并集发送给主库。主库会使用从库请求的GTID集合和自己的gtid_executed比较,把从库GTID集合里缺失的事务全都发送给从库。如果从库缺失的GTID,已经被主库pruge了呢?从库报1236错误,IO线程中断。
通过GTID找到点儿的原理还是比较奇怪的,它过于强调主从binlog中GTID集合的一致性,弱化了Binlog执行的顺序性。
业界经验:
------------------------------------------------------------------------------------------------------------------------------------------
二、GTID的组成部分:
前面是server_uuid:后面是一个序列号
例如:server_uuid:sequence number
7800a22c-95ae-11e4-983d-080027de205a:10
UUID:每个mysql实例的唯一ID,由于会传递到slave,所以也可以理解为源ID。
Sequence number:在每台MySQL服务器上都是从1开始自增长的序列,一个数值对应一个事务。
GTID比传统复制的优势:
1、更简单的实现failover,不用以前那样在需要找log_file和log_Pos。
2、更简单的搭建主从复制。
3、比传统复制更加安全。
4、GTID是连续没有空洞的,因此主从库出现数据冲突时,可以用添加空事物的方式进行跳过。
GTID的工作原理:
1、master更新数据时,会在事务前产生GTID,一同记录到binlog日志中。
2、slave端的i/o 线程将变更的binlog,写入到本地的relay log中。
3、sql线程从relay log中获取GTID,然后对比slave端的binlog是否有记录。
4、如果有记录,说明该GTID的事务已经执行,slave会忽略。
5、如果没有记录,slave就会从relay log中执行该GTID的事务,并记录到binlog。
6、在解析过程中会判断是否有主键,如果没有就用二级索引,如果没有就用全部扫描。
要点:
1、slave在接受master的binlog时,会校验master的GTID是否已经执行过(一个服务器只能执行一次)。
2、为了保证主从数据的一致性,多线程只能同时执行一个GTID。
-----------------------------------------------------------------------------------------------------------------------------------------
二、GTID生命周期
1、Master产生GTID. --->2、发送Binlog信息到从库上----》3、SLAVE执行GTID.----->4、SLAVE不生成GTID
三、GTID的维护和搭建主从的必须的参数
在MySQL5.7.5 以上版本 ,mysql库中新增了 表gtid_executed;.表里每一行表示你一个GTID 或GTID集合。 包括了source_uuid、集合开始和结束的事务ID.
只有开启GTID,相应的信息才会保存到 该表中。当服务器异常关闭,GTID不会保存到mysql.gtid.executed 系统变量, 只有在恢复时,这些个GTID信息才会添加到表(gtid_executed).
注意:resert master 操作会清空gtid_executed.'
数据库不断更新,此表也会变的异常巨大,需要进行压缩和空间处理,一般处理可以通过事务的间隔来代替原来的每个GTID信息,来缩减磁盘空间的消耗。
当启用GTID时候,会定期的对gtid_executed 表进行压缩。可以通过设置参数,executed_gtids_compression_period 变量来控制在压缩表之前允许的事务数。从而控制压缩率。改参数的默认值是1000.。表示1000个事务之后执行压缩。设置为零表示不压缩。
搭建GTID主从之前必备的参数和条件哪些?
1、必须开启binlog(log-bin)
2、log-slave-updates=1 (记录回放的action到自己的binlog日志里面,很重要一定要设置)
3、binlog_format =ROW (这个必须是ROW,没啥好说的)
3、配置不同的server_id
4、开启GTID模式(gtid_mode=ON)-
5、保证gitd的一致性(enforce_gtid_consitency=ON)
6、skip-slave-start =1 (当salve数据库启动的时候,不会自动开启复制)
开启GTID的正确姿势:
1、关闭master的写入,保证主从数据一致。
2、在slave上配置参数(skip-slave-start =1),避免slave重启,自动使用传统复制模式。
3、修改配文件。
4、重启所有的MySQL数据库。
上述针对MySQL5.7.6之前的版本,(需要重启所有数据库的,给新手DBA的忠告,重启大体量级别的数据库,可能花费的时间不止2个小时,如果出现意外,花数个小时都有可能,需要考量到后果)
如果MySQL版本是5.7.6之后的版本,可以在线调整,就无须中断 业务访问的风险了。
在线调整将传统复制改为GITD复制 8步骤:
1、set @@global.enforce_gtid_consistency=warn;
2、set @@global.enforce_gtid_consistency=on;
3、SET @@GLOBAL.GTID_MODE = OFF_PERMISSIVE;
4、SET @@GLOBAL.GTID_MODE = on_permissive;
5、SHOW STATUS LIKE 'ONGOING_ANONYMOUS_TRANSACTION_COUNT'; (检查所有实例都为零的时候)在进行下一步
6、SET @@GLOBAL.GTID_MODE = ON;
7、在线修改my.cnf的配置,防止数据库重启,配置失效。
8、stop slave; change master to master_auto_position=1; start slave;
改回来传统;
1、stop slave; change master to master_auto_position=0;
2、change master 基于binlog位置的
3、start slave
4、SET @@GLOBAL.GTID_MODE = on_permissive;
5、SET @@GLOBAL.GTID_MODE = OFF_PERMISSIVE;
6、等待@@GLOBAL.GTID_OWNED为空,等待所有 slave 都复制完成匿名事务
7、SET @@GLOBAL.GTID_MODE = OFF;
8、修改配置文件:gitd_mode =off ; enforce_gtid_consistency=OFF
开启GITD主从有三种情况
1、master是新搭建的无数据,直接CHANGE MASTER 语句搞定。
2、运行不久的MASTER ,所有的binlog保存完整,也可直接CHAGE MASTER (注意:如果binlog多,slave同步时间会很长,网络压力也很大)
3、MASTER运行很久,有大量数据。(因最原始的binlog被删除,无法从MASTER获取所有GTID信息,那就只能跳过)
利用备份返回获取MASTER的数据及GTID范围,该信息会保留在xtrabackup_binlog_info文件中。
利用备份数据,搭建slave实例。启动slave实例,并设置gtid_purged的值,跳过这段丢失的范围。
命令为:SET @@GLOBA.GTID_PURGED='uuid:interval[-interval]' 然后执行change master语句。
并启动复制,slave会自动跳过gtid的范围,拉取最新的GITD.
如何跳过 一个GTID
为什么要跳过呢,因为主键、索引冲突,从库找不到这条记录等等。
首先我们通过分析binlog日志找到发生错误和冲突的是哪个事务,*(此处加※号,不管你是研发,测试,还是DBA,如果还只停留在会基本的使用,那在未来不会有任何竞争力)。有很多企业数据库版本还停留在5.5 ,5.1 我相信被数据库的问题,折磨的企业不是一两家吧~。重点在于不敢有版本跳跃式的更新的规划。如果所在的企业升级到5.7版本之后,面临的就是GTID的使用,不用也是可以哒。GTID是MySQL5.7复制的新特性,谁先用那必定谁先走坑,啊哈哈~。
通过show slave statusG找到冲突的GTID号.
然后执行
SET gtid_next = '冲突的GTID号';
BEGIN;COMMIT;
SET gtid_next = 'AUTOMATIC';
START SLAVE;
这就可以跳过一个事务了,原理在于通过执行一个空事务代替master传递过来的冲突事务.
检查 show slave status
改变GTID主从复制关系,少了一步show master status 里面去看 binlog 复制点。比较方便了。
stop slave;
change master to master_host='ip' ,master_port=3306,master_auto_position=1;
start slave;
注意:它的缺点。不支持混合存储引擎。不支持CREATE TABLE ....SELECT 、CREATE_TRMPORARY 和 DROP TEM,PORARY TABLE。不推荐使用mysql_upgrade.
以上部分内容转载至: