乐趣区

关于mysql:基于GTID的主备切换

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 曾经被删除掉了,导致主备创立不胜利。这种状况下,你感觉能够怎么解决呢?

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

退出移动版