GTID

通过sql_slave_skip_counter跳过事务和通过slave_skip_errors疏忽谬误的办法,尽管都最终能够建设从库B和新主库A’的主备关系,但这两种操作都很简单,而且容易出错。所以,MySQL 5.6版本引入了GTID,彻底解决了这个艰难。

那么,GTID到底是什么意思,又是如何解决找同步位点这个问题呢?当初,我就和你简略介绍一下。

GTID的全称是Global Transaction Identifier,也就是全局事务ID,是一个事务在提交的时候生成的,是这个事务的惟一标识。它由两局部组成,格局是:

GTID=server_uuid:gno 

其中:

  • server_uuid是一个实例第一次启动时主动生成的,是一个全局惟一的值;
  • gno是一个整数,初始值是1,每次提交事务的时候调配给这个事务,并加1。

这里我须要和你阐明一下,在MySQL的官网文档里,GTID格局是这么定义的:

GTID=source_id:transaction_id 

这里的source_id就是server_uuid;而前面的这个transaction_id,我感觉容易造成误导,所以我改成了gno。为什么说应用transaction_id容易造成误会呢?

因为,在MySQL外面咱们说transaction_id就是指事务id,事务id是在事务执行过程中调配的,如果这个事务回滚了,事务id也会递增,而gno是在事务提交的时候才会调配。

从成果上看,GTID往往是间断的,因而咱们用gno来示意更容易了解。

GTID模式的启动也很简略,咱们只须要在启动一个MySQL实例的时候,加上参数gtid_mode=on和enforce_gtid_consistency=on就能够了。

在GTID模式下,每个事务都会跟一个GTID一一对应。这个GTID有两种生成形式,而应用哪种形式取决于session变量gtid_next的值。

  1. 如果gtid_next=automatic,代表应用默认值。这时,MySQL就会把server_uuid:gno调配给这个事务。
    a. 记录binlog的时候,先记录一行 SET @@SESSION.GTID_NEXT=‘server_uuid:gno’;
    b. 把这个GTID退出本实例的GTID汇合。
  2. 如果gtid_next是一个指定的GTID的值,比方通过set gtid_next='current_gtid’指定为current_gtid,那么就有两种可能:
    a. 如果current_gtid曾经存在于实例的GTID汇合中,接下来执行的这个事务会间接被零碎疏忽;
    b. 如果current_gtid没有存在于实例的GTID汇合中,就将这个current_gtid调配给接下来要执行的事务,也就是说零碎不须要给这个事务生成新的GTID,因而gno也不必加1。

留神,一个current_gtid只能给一个事务应用。这个事务提交后,如果要执行下一个事务,就要执行set 命令,把gtid_next设置成另外一个gtid或者automatic。

这样,每个MySQL实例都保护了一个GTID汇合,用来对应“这个实例执行过的所有事务”。

这样看上去不太容易了解,接下来我就用一个简略的例子,来和你阐明GTID的根本用法。

咱们在实例X中创立一个表t。

CREATE TABLE `t` (  `id` int(11) NOT NULL,  `c` int(11) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB;insert into t values(1,1); 

图4 初始化数据的binlog

能够看到,事务的BEGIN之前有一条SET @@SESSION.GTID_NEXT命令。这时,如果实例X有从库,那么将CREATE TABLE和insert语句的binlog同步过来执行的话,执行事务之前就会先执行这两个SET命令, 这样被退出从库的GTID汇合的,就是图中的这两个GTID。

假如,当初这个实例X是另外一个实例Y的从库,并且此时在实例Y上执行了上面这条插入语句:

insert into t values(1,1); 

并且,这条语句在实例Y上的GTID是 “aaaaaaaa-cccc-dddd-eeee-ffffffffffff:10”。

那么,实例X作为Y的从库,就要同步这个事务过去执行,显然会呈现主键抵触,导致实例X的同步线程进行。这时,咱们应该怎么解决呢?

解决办法就是,你能够执行上面的这个语句序列:

set gtid_next='aaaaaaaa-cccc-dddd-eeee-ffffffffffff:10';begin;commit;set gtid_next=automatic;start slave; 

其中,前三条语句的作用,是通过提交一个空事务,把这个GTID加到实例X的GTID汇合中。如图5所示,就是执行完这个空事务之后的show master status的后果。

图5 show master status后果

能够看到实例X的Executed_Gtid_set外面,曾经退出了这个GTID。

这样,我再执行start slave命令让同步线程执行起来的时候,尽管实例X上还是会继续执行实例Y传过来的事务,然而因为“aaaaaaaa-cccc-dddd-eeee-ffffffffffff:10”曾经存在于实例X的GTID汇合中了,所以实例X就会间接跳过这个事务,也就不会再呈现主键抵触的谬误。

在下面的这个语句序列中,start slave命令之前还有一句set gtid_next=automatic。这句话的作用是“复原GTID的默认调配行为”,也就是说如果之后有新的事务再执行,就还是依照原来的调配形式,持续调配gno=3。

基于GTID的主备切换

当初,咱们曾经了解GTID的概念,再一起来看看基于GTID的主备复制的用法。

在GTID模式下,备库B要设置为新主库A’的从库的语法如下:

CHANGE MASTER TO MASTER_HOST=$host_name MASTER_PORT=$port MASTER_USER=$user_name MASTER_PASSWORD=$password master_auto_position=1 

其中,master_auto_position=1就示意这个主备关系应用的是GTID协定。能够看到,后面让咱们头疼不已的MASTER_LOG_FILE和MASTER_LOG_POS参数,曾经不须要指定了。

咱们把当初这个时刻,实例A’的GTID汇合记为set_a,实例B的GTID汇合记为set_b。接下来,咱们就看看当初的主备切换逻辑。

咱们在实例B上执行start slave命令,取binlog的逻辑是这样的:

  1. 实例B指定主库A’,基于主备协定建设连贯。
  2. 实例B把set_b发给主库A’。
  3. 实例A’算出set_a与set_b的差集,也就是所有存在于set_a,然而不存在于set_b的GITD的汇合,判断A’本地是否蕴含了这个差集须要的所有binlog事务。
    a. 如果不蕴含,示意A’曾经把实例B须要的binlog给删掉了,间接返回谬误;
    b. 如果确认全副蕴含,A’从本人的binlog文件外面,找出第一个不在set_b的事务,发给B;
  4. 之后就从这个事务开始,往后读文件,按程序取binlog发给B去执行。

其实,这个逻辑外面蕴含了一个设计思维:在基于GTID的主备关系里,零碎认为只有建设主备关系,就必须保障主库发给备库的日志是残缺的。因而,如果实例B须要的日志曾经不存在,A’就回绝把日志发给B。

这跟基于位点的主备协定不同。基于位点的协定,是由备库决定的,备库指定哪个位点,主库就发哪个位点,不做日志的完整性判断。

基于下面的介绍,咱们再来看看引入GTID后,一主多从的切换场景下,主备切换是如何实现的。

因为不须要找位点了,所以从库B、C、D只须要别离执行change master命令指向实例A’即可。

其实,谨严地说,主备切换不是不须要找位点了,而是找位点这个工作,在实例A’外部就曾经主动实现了。但因为这个工作是主动的,所以对HA零碎的开发人员来说,十分敌对。

之后这个零碎就由新主库A’写入,主库A’的本人生成的binlog中的GTID汇合格局是:server_uuid_of_A’:1-M。

如果之前从库B的GTID汇合格局是 server_uuid_of_A:1-N, 那么切换之后GTID汇合的格局就变成了server_uuid_of_A:1-N, server_uuid_of_A’:1-M。

当然,主库A’之前也是A的备库,因而主库A’和从库B的GTID汇合是一样的。这就达到了咱们预期。

GTID和在线DDL

接下来,我再举个例子帮你了解GTID。

之前在第22篇文章《MySQL有哪些“饮鸩止渴”进步性能的办法?》中,我和你提到业务高峰期的慢查问性能问题时,剖析到如果是因为索引缺失引起的性能问题,咱们能够通过在线加索引来解决。然而,思考到要防止新增索引对主库性能造成的影响,咱们能够先在备库加索引,而后再切换。

过后我说,在双M构造下,备库执行的DDL语句也会传给主库,为了防止传回后对主库造成影响,要通过set sql_log_bin=off关掉binlog。

评论区有位同学提出了一个问题:这样操作的话,数据库外面是加了索引,然而binlog并没有记录下这一个更新,是不是会导致数据和日志不统一?

这个问题提得十分好。过后,我在留言的回复中就援用了GTID来阐明。明天,我再和你开展阐明一下。

假如,这两个互为主备关系的库还是实例X和实例Y,且以后主库是X,并且都关上了GTID模式。这时的主备切换流程能够变成上面这样:

  • 在实例X上执行stop slave。
  • 在实例Y上执行DDL语句。留神,这里并不需要敞开binlog。
  • 执行实现后,查出这个DDL语句对应的GTID,并记为 server_uuid_of_Y:gno。
  • 到实例X上执行以下语句序列:
set GTID_NEXT="server_uuid_of_Y:gno";begin;commit;set gtid_next=automatic;start slave; 

这样做的目标在于,既能够让实例Y的更新有binlog记录,同时也能够确保不会在实例X上执行这条更新。

  • 接下来,执行完主备切换,而后照着上述流程再执行一遍即可。

小结

在明天这篇文章中,我先和你介绍了一主多从的主备切换流程。在这个过程中,从库找新主库的位点是一个痛点。由此,咱们引出了MySQL 5.6版本引入的GTID模式,介绍了GTID的基本概念和用法。

能够看到,在GTID模式下,一主多从切换就十分不便了。

因而,如果你应用的MySQL版本反对GTID的话,我都倡议你尽量应用GTID模式来做一主多从的切换。

在下一篇文章中,咱们还能看到GTID模式在读写拆散场景的利用。

最初,又到了咱们的思考题工夫。

你在GTID模式下设置主从关系的时候,从库执行start slave命令后,主库发现须要的binlog曾经被删除掉了,导致主备创立不胜利。这种状况下,你感觉能够怎么解决呢?

你能够把你的办法写在留言区,我会在下一篇文章的开端和你探讨这个问题。感激你的收听,也欢送你把这篇文章分享给更多的敌人一起浏览。