分类: MySQL
MySQL GTID的原理

GTID介绍
GTID是MySQL 5.6的新特性,其全称是Global Transaction Identifier,可简化MySQL的主从切换以及Failover。GTID用于在binlog中唯一标识一个事务。当事务提交时,MySQL Server在写binlog的时候,会先写一个特殊的Binlog Event,类型为GTID_Event,指定下一个事务的GTID,然后再写事务的Binlog。主从同步时GTID_Event和事务的Binlog都会传递到从库,从库在执行的时候也是用同样的GTID写binlog,这样主从同步以后,就可通过GTID确定从库同步到的位置了。也就是说,无论是级联情况,还是一主多从情况,都可以通过GTID自动找点儿,而无需像之前那样通过File_name和File_position找点儿了。支持多线程复制(多数据库同时复制才有意义,仅仅复制一个则没有意义)
通常,由于读取较大,主负责数据的写入,从负责的读取,可以有多从。
GTID展现

GTID = server_uuid:transaction_id
server_uuid 来源于 auto.cnf
GTID: 在一组复制中,全局唯一
mysqlreplicate: 快速调入一个从节点,并且成为GTID中的从节点
mysqlrplcheck: 简单的校验,在ha性能时能够检查节点,那些更易用,更完整的提省为主节点
mysqlrplshow:显示发现拓扑结构
mysqlfailover:能够实现,手动或自动实现故障转移,将从节点提升为主节点
mysqlrpladmin: 实现管理调度

GTID工作原理

1、当一个事务在主库端执行并提交时,产生GTID,一同记录到binlog日志中。
2、binlog传输到slave,并存储到slave的relaylog后,读取这个GTID的这个值设置gtid_next变量,即告诉Slave,下一个要执行的GTID值。
3、sql线程从relay log中获取GTID,然后对比slave端的binlog是否有该GTID。
4、如果有记录,说明该GTID的事务已经执行,slave会忽略。
5、如果没有记录,slave就会执行该GTID事务,并记录该GTID到自身的binlog,
   在读取执行事务前会先检查其他session持有该GTID,确保不被重复执行。
6、在解析过程中会判断是否有主键,如果没有就用二级索引,如果没有就用全部扫描。

GTID的优势

1、更简单的实现failover,不用以前那样在需要找log_file和log_pos。
2、更简单的搭建主从复制。
3、比传统的复制更加安全。
4、GTID是连续的没有空洞的,保证数据的一致性,零丢失。
使用server_uuid和transaction_id两个共同组成一个GTID。即:GTID = server_uuid:transaction_id
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;

通过SHOW SLAVE STATUS也可以看到Auto_Position: 1,说明以后START SLAVE将使用GTID自动找点儿,开启GTID之后原理上还支持使用FileName和FilePosition的方式找点儿,但是不建议使用。如果非要使用的话,在CHANGE MASTER的时候要指定MASTER_AUTO_POSITION=0
MySQL通过若干变量可以查看GTID的执行情况:

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 rows in set (0.00 sec)

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中的结构
binlog_gtid.jpg
GTID event 结构
gtid_event.jpg
Previous_gtid_log_event 在每个binlog 头部都会有
每次binlog rotate的时候存储在binlog头部
Previous-GTIDs在binlog中只会存储在这台机器上执行过的所有binlog,不包括手动设置gtid_purged值。
换句话说,如果你手动set global gtid_purged=xx; 那么xx是不会记录在Previous_gtid_log_event中的。
GTID和Binlog之间的关系是怎么对应的呢
如何才能找到GTID=?对应的binlog文件呢?

* 假设有4个binlog: bin.001,bin.002,bin.003,bin.004
* bin.001 : Previous-GTIDs=empty; binlog_event有:1-40  
* bin.002 : Previous-GTIDs=1-40;  binlog_event有:41-80 
* bin.003 : Previous-GTIDs=1-80;  binlog_event有:81-120  
* bin.004 : Previous-GTIDs=1-120;  binlog_event有:121-160  
  1. 假设现在我们要找GTID=$A,那么MySQL的扫描顺序为: 从最后一个binlog开始扫描(即:bin.004)
  2. bin.004的Previous-GTIDs=1-120,如果$A=140 > Previous-GTIDs,那么肯定在bin.004中
  3. bin.004的Previous-GTIDs=1-120,如果$A=88 包含在Previous-GTIDs中,那么继续对比上一个binlog文件 bin.003,然后再循环前面2个步骤,直到找到为止
    开启GTID的必备条件
    MySQL 5.6

    gtid_mode=ON(必选)    
    log_bin=ON(必选)    
    log-slave-updates=ON(必选)    
    enforce-gtid-consistency(必选)
    MySQL5.7.13 or higher
    
    gtid_mode=ON(必选)  
    enforce-gtid-consistency(必选)
    log_bin=ON(可选)--高可用切换,最好设置ON  
    log-slave-updates=ON(可选)--高可用切换,最好设置ON

    1.1.5 新的复制协议 COM_BINLOG_DUMP_GTID
    slave会将已经执行过的gtid,以及以及接受到relay log中的gtid的并集发送给master
    更多GTID文档:
    http://dev.mysql.com/doc/refman/5.7/en/change-master-to.html
    UNION(@@global.gtid_executed, Retrieved_gtid_set - last_received_GTID)
    MySQL官方GTID文档:https://dev.mysql.com/doc/refman/5.7/en/replication-gtids-concepts.html
    http://keithlan.github.io/2016/06/23/gtid/
    http://hamilton.duapp.com/detail?articleId=47


相关博文:

发表新评论