与MySQL传统复制相比,GTID有哪些独特的复制姿势?

图片 14

背景:MySQL5.6.40,库比较小,row+gtid复制环境,但由于以前种种原因,备份还原在从库后,开启复制存在大量1062,1032错误,gtid卡在靠前位置。做复制的时候没有任何从库,每小时的备份也被运维停了。

要讨论如何恢复从库,我们得先来了解如下一些概念:

与MySQL传统复制相比,GTID有哪些独特的复制姿势?

以前从来没遇到过这种情况,相对测试环境正式环境比较复杂,而且猜测可能是之前备份还原从来没用过备份一致性参数导致,并且发现错误也没有手工检查(这个问题还在研究中,有遇到并知道原因的小伙伴欢迎指导)。

GTID_EXECUTED:它是一组包含已经记录在二进制日志文件中的事务集合

为了今后避免因为恢复不及时导致的数据丢失,特别总结本次故障过程和大家讨论、分享。

GTID_PURGED:它是一组包含已经从二进制日志删除掉的事务集合。

陈华军,苏宁云商IT总部资深技术经理,从事数据库服务相关的开发和维护工作,之前曾长期从事富士通关系数据库的开发,PostgreSQL中国用户会核心成员,熟悉PostgreSQL和MySQL。

简化时间轴如下图:

 

 

开始—->备份主库—->恢复从库—->复制error1032,1062—->删除从库再次恢复—->复制error1032,1062—->reset
master从库、主库—->准备删除从库—->误操作删主库—–>恢复主库—–>跳过大量1062、1032错误—->找drop
db位置恢复从库—->对比主从数据—->手工补数据—->结束

 

 

下面按照我的记忆描述下当时的场景:

在继续讨论时,我们先来看下如何新建一个基于GTID的slave。

[MySQL 5.6] GTID实现、运维变化及存在的bug

一、首次备份主库、搭建从库

第一次搭建从库,从主库的备份未使用master-data=2
single-transaction(保证事务备份时的一致性)参数迁移后,报大量1062和1032错误(家家有本难念的经,不多说了)

 

图片 1

通过了解上面的两个参数,我们现在只需要:

二、第二次还原主库到从库

于是第二次重新导入。

同样报错。在导入从库前使用reset master;将从库binlog清除。

由于操作人员不了解reset master含义及执行结果,又在主库做了reset master;

结果导致主库所有binlog日志被清除并且binlog position置为1;

这里贴以下官方说明,别没事干就在主库上用这条。

 

图片 2

再次导入发现依旧大量报1032,1062错误。

由于怀疑是因为备份时没使用–single-transaction参数,准备删除从库,加参数重新备份主库。

1.从主库上做一个备份时记录备份时gtid_executed的值。

 

三、误删除主库

结果误操作删除主库(这个锅一部分原因要甩给mysql
naivcat这个工具,垂直排列库,稍微不注意就容易点错。还是建议大家听吴老师的用官方的workbench),删库还是两人校对,在操作系统上执行,删前没把握最好备份一遍。

删库这种操作谨慎谨慎再谨慎,重要的事情说三遍!

删库这种操作谨慎谨慎再谨慎,重要的事情说三遍!

删库这种操作谨慎谨慎再谨慎,重要的事情说三遍!

drop database;(在naivcat上右键删除库,但binlog日志中还是会记录DROP
DATABASE这条记录)

这时候为了保证业务不中断,立马在主库上通过之前的备份文件恢复了一套库,当然数据肯定丢失了,但可以推算丢失数据的时间段(从备份完毕开始—>DROP
DATABASE)。

PS.请不要问我为什么删库,为什么删完又恢复了一套库,因为都不是我干的。。。。。。

万幸的是误删除主库但并未删除从库,而且从库的io_thread仍然处于yes状态(回顾吴老师的课程,也就是说虽然库被删除了但其实删库前的数据=备份数据+io_thread已下载的删除主库前的数据),由于sql_thread仍然停到gtid靠前的位置

 

图片 3

2.在新的slave上恢复此备份时设置从库的gtid_purged的值为备份时master上gtid_executed的值。

适当减小binlog文件的大小
如果开启GTID,理论上最好调小每个binlog文件的最大值,以缩小扫描文件的时间。

四、跳过大量1032,1062错误

这个时候只要看下备份文件的gtid位置,并purge到该位置(之前备份丢了,随便找了一个备份的截图,理解万岁)。

##这里说明一下为什么直接purge到备份的结尾位置,因为书库备份的数据中1032和1062错误太多,且主库已经删除没办法通过脚本对比跳过大量1032,1062错误(吴老师友情提供),在能够保证是从主库逻辑备份过来的情况下(主从数据一致),我们选择快速跳过大量错误(偷懒加情况急),直接purge到备份最后的位置。

 

图片 4

##上图是随便截的一个备份文件最开头的位置,请忽略那个gtid的值,意思明白就行。

set @@gtid_purged=’fb1f83af-1915-11e8-811b-000c29c4d77d:1-500′;

注:‘500’代表备份文件最后一个执行的事务的gtid。gtid_purged代表数据库已经在从库上重放过1-500这段事务。

 

 

五、找到主库DROP DATABASE的GTID位置

purge到该位置然后再确定drop
database的位置上(思路:如果不确定dropdatabase的位置就start slave
那么从库会应用主库的binlog也就会执行主库drop
database的操作,为了避免从库重放主库drop
database的操作,我们要设法让gtid在从库停到drop
database前一个gtid的位置)

注:可以通过大致删库时间或者从从库的show slave
statusG上看到主库的binlog位置从后往前找DROP
DATABASE的位置,如果删库后做了reset
master那就只能从从库的relay-bin-log上找了(切记主库没事别reset
master);

mysqlbinlog    -vvv  –base64-output=decode-rows  relay-bin.000017

 

图片 5

通过mysqldump可以完成我们需要的功能。

GTID(Global Transaction
ID)是MySQL5.6引入的功能,可以在集群全局范围标识事务,用于取代过去通过binlog文件偏移量定位复制位置的传统方式。借助GTID,在发生主备切换的情况下,MySQL的其它Slave可以自动在新主上找到正确的复制位置,这大大简化了复杂复制拓扑下集群的维护,也减少了人为设置复制位置发生误操作的风险。另外,基于GTID的复制可以忽略已经执行过的事务,减少了数据发生不一致的风险。

六、启动从库SQL_THREAD

在从库上执行start slave sql_thread
until的命令,这里需要说明,因为主库已经还原,业务跑起来了,这时候开启io_thread没有什么意义,所以只用让从库的sql_thread线程重放DROP
DATABASE之前的事务就行。

root@localhost[{none}]>start slave sql_thread until
sql_before_gtid=’fb1f83af-1915-11e8-811b-000c29c4d77d:2343′;

启动slave,并且让从库gtid停在主库drop
database操作之前一个gtid就可以,再还原到主库就能立马投入使用,还不会导致数据丢失。

 

图片 6

确保从库executed_gtid_set到了我们before的前一个值就可以备份了,然后dump这份数据还原主库,当然如果从库性能不错的话可以考虑应用端更改连接,这样速度更快一些。

但比较麻烦的就是,要保证生产的实时性,删库后立即在主库上还原了之前用来恢复从库的备份文件,这就肯定会导致中间数据丢失。

 

GTID虽好,要想运用自如还需充分了解其原理与特性,特别要注意与传统的基于binlog文件偏移量复制方式不一样的地方。本文概述了关于GTID的几个常见问题,希望能对理解和使用基于GTID的复制有所帮助。

七、数据对比还原

这时候只能使用用之前用来搭建从库的备份再恢复一个库,再用pt-table-checksum对比主库和恢复库,从库和恢复库不一致的数据,用pt-table-sync生成对应语句。然后手工把数据补进系统中。

对比1:主库:备份数据还原的库—->目标:找到主库在删库之后应用又写入了哪些数据。

对比2:从库:备份数据还原的库—->目标:找到备份数据之后,删库之前应用在主库里写了哪些数据。

因为量不是很大,手工对比一下就行,当然数据还原的坑也有很多,不过基本上都被研发填了。

 

GTID长什么样

总结:

头一回碰到删库情况还是有点蒙,还好主库用的是GTID找binlog日志中的位置相对容易一点。这次恢复最幸运的就是还好从库卡在靠前的位置,要不然即使有了从库,数据也会被删了,恢复起来相对更麻烦些。

对于gtid的恢复,课上吴炳锡老师都讲过,但是一上手还是慢了几拍,还是要通过实战多练习加深手感避免在真实情况下懵逼。

最后特别鸣谢:知数堂叶金荣老师和吴炳锡老师在故障发生时给予的帮助和支持。

转载请注明出处

目前主库上的状态(3301):

根据官方文档定义,GTID由source_id加transaction_id构成。
GTID = source_id:transaction_id

图片 7

上面的source_id指示发起事务的MySQL实例,值为该实例的server_uuidserver_uuid由MySQL在第一次启动时自动生成并被持久化到auto.cnf文件里,transaction_id是MySQL实例上执行的事务序号,从1开始递增
例如:
e6954592-8dba-11e6-af0e-fa163e1cf111:1

[zejin] 3301>show global variables like 'gtid_executed';
+---------------+-------------------------------------------+
| Variable_name | Value |
+---------------+-------------------------------------------+
| gtid_executed | a97983fc-5a29-11e6-9d28-000c29d4dc3f:1-15 |
+---------------+-------------------------------------------+
1 row in set (0.00 sec)

[zejin] 3301>show global variables like 'gtid_purged';
+---------------+-------------------------------------------+
| Variable_name | Value |
+---------------+-------------------------------------------+
| gtid_purged | a97983fc-5a29-11e6-9d28-000c29d4dc3f:1-13 |
+---------------+-------------------------------------------+
1 row in set (0.00 sec)

一组连续的事务可以用’-‘连接的事务序号范围表示。例如
e6954592-8dba-11e6-af0e-fa163e1cf111:1-5

图片 8

更一般的情况是GTID的集合。GTID集合可以包含来自多个source_id的事务,它们之间用逗号分隔;如果来自同一source_id的事务序号有多个范围区间,各组范围之间用冒号分隔,例如:
e6954592-8dba-11e6-af0e-fa163e1cf111:1-5:11-18,
e6954592-8dba-11e6-af0e-fa163e1cf3f2:1-27

 

即,GTID集合拥有如下的形式定义:
gtid_set:
    uuid_set [, uuid_set] …
    | ”

 

uuid_set:
    uuid:interval[:interval]…

 

uuid:
    hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh

step1:用mysqldump做一个全备

h:
    [0-9|A-F]

mysqldump –all-databases –single-transaction –triggers –routines
–events –host=127.0.0.1 –port=3301 –user=root –password=123 >
dump3301.sql

interval:
    n[-n]

 

    (n >= 1)

打开dump3301.sql我们可以看到如下语句:

如何查看GTID

SET @@GLOBAL.GTID_PURGED=’a97983fc-5a29-11e6-9d28-000c29d4dc3f:1-15′;

可以通过MySQL的几个变量查看相关的GTID信息。

此值即为master3301上gtid_executed的值。

gtid_executed
在当前实例上执行过的GTID集合;
实际上包含了所有记录到binlog中的事务。所以,设置set
sql_log_bin=0后执行的事务不会生成binlog
事件,也不会被记录到gtid_executed中。执行RESET
MASTER可以将该变量置空(搭主从时候,导入数据到从库不能导入就执行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。可能的取值如下:
1)AUTOMATIC:
自动生成下一个GTID,实现上是分配一个当前实例上尚未执行过的序号最小的GTID
2)ANONYMOUS:
设置后执行事务不会产生GTID。
3)显式指定的GTID:
可以指定任意形式合法的GTID值,但不能是当前gtid_executed中的已经包含的GTID,否则,下次执行事务时会报错。

step2:全新启动一个新的库3303,注意在配置文件中配置enforce_gtid_consistency及gtid_mode=on

这些变量可以通过show命令查看,比如:

图片 9

show variables like ‘%gtid%’;
+———————————+———–+
| Variable_name | Value |
+———————————+———–+
| binlog_gtid_simple_recovery | ON |
| enforce_gtid_consistency | ON |
| gtid_executed | |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | |
| simplified_binlog_gtid_recovery | ON |
+———————————+———–+

mysqld_safe --defaults-file=/home/mysql/my3303.cnf &
此时新库3303上的状态应该是这样的:

[(none)] 3303>show global variables like 'gtid_executed';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_executed | |
+---------------+-------+
1 row in set (0.01 sec)

[(none)] 3303>show global variables like 'gtid_purged';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_purged | |
+---------------+-------+
1 row in set (0.00 sec)

如何产生GTID

图片 10

GTID的生成受gtid_next控制。
在Master上,gtid_next是默认的AUTOMATIC,即在每次事务提交时自动生成新的GTID。它从当前已执行的GTID集合(即gtid_executed)中,找一个大于0的未使用的最小值作为下个事务GTID。同时在binlog的实际的更新事务事件前面插入一条set
gtid_next事件。

 

以下是一条insert语句生成的binlog记录:
图片 11

 

在Slave上回放主库的binlog时,先执行set gtid_next
…,然后再执行真正的insert语句,确保在主和备上这条insert对应于相同的GTID。

step3:导入备份文件并查看状态值:

一般情况下,GTID集合是连续的,但使用多线程复制(MTS)以及通过gtid_next进行人工干预时会导致gtid空洞。比如下面这样:
图片 12

图片 13

继续执行事务,MySQL会分配一个最小的未使用GTID,也就是从出现空洞的地方分配GTID,最终会把空洞填上。

mysql -uroot -h127.0.0.1 -p123 -P3303 < dump3301.sql
[(none)] 3303>show global variables like 'gtid_executed';
+---------------+-------------------------------------------+
| Variable_name | Value |
+---------------+-------------------------------------------+
| gtid_executed | a97983fc-5a29-11e6-9d28-000c29d4dc3f:1-15 |
+---------------+-------------------------------------------+
1 row in set (0.02 sec)

[(none)] 3303>show global variables like 'gtid_purged';
+---------------+-------------------------------------------+
| Variable_name | Value |
+---------------+-------------------------------------------+
| gtid_purged | a97983fc-5a29-11e6-9d28-000c29d4dc3f:1-15 |
+---------------+-------------------------------------------+
1 row in set (0.00 sec)

图片 14

图片 15

这意味着严格来说我们即不能假设GTID集合是连续的,也不能假定GTID序号大的事务在GTID序号小的事务之后执行,事务的顺序应由事务记录在binlog中的先后顺序决定。

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图