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找点儿了。html
MySQL 5.6使用server_uuid和transaction_id两个共同组成一个GTID。即:GTID = server_uuid:transaction_idmysql
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中的横线只是为了便于查看,并无实际意义)。算法
1
2
3
4
5
6
7
|
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。sql
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模式。bash
刚才提到,同一个server_uuid下的transaction_id通常是递增的,难道在某些状况下不是递增的吗?答案是确定的。MySQL支持经过设置Session级别的变量gtid_next,来指定下一个事务的GTID,格式就是‘server_uuid:transaction_id'。以后还能够改回AUTOMATIC(默认值)app
1
2
3
4
5
6
7
8
|
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的大小来判断事务的顺序。less
使用server_uuid:transaction_id共同组成一个GTID的好处是,因为server_uuid惟一,即便一个集群内多个节点同时有写入,也不会形成GTID冲突。post
MySQL经过全局变量gtid_mode控制开启/关闭GTID模式。可是gtid_mode是只读的,可添加到配置文件中,而后重启mysqld来开启GTID模式。相关配置项以下:ui
1
2
3
4
5
|
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。命令以下:
1
2
3
4
5
6
|
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的执行状况
1
2
3
4
5
6
7
8
9
10
|
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)
|
这里有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的格式,首先看一段Binlog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#
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模式的状况下,从库用(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模式同步,小明想给集群增长一台从库,新作完一台从库,数据和主库一致,可是没有binlog,也就是说新从库的@@global.gtid_executed是空的。可是CHANGE MASTER时能够经过File_name和File_pos找到正确的同步点,而后START SLAVE,一切正常。过了一下子,小明以为还能够经过MASTER_AUTO_POSITION = 1的方式从新CHANGE MASTER,而后再START SLAVE。这种状况下,主库一看从库GTID里少了那么多binlog,而后把所有缺失的binglog再给从库发送一遍,那么悲剧就发生了。
为了解决这个问题,小明新作完从库之后,应该在从库上执行reset master; set global gtid_purged = 'xxxxx',把缺失的GTID集合设置为purged,而后就能够直接使用MASTER_AUTO_POSITION=1自动找点儿了。
因而可知,开启GTID之后,Binlog和数据文件同样重要,不只要求主从数据一致,还要求主从Binlog中GTID集合一致。
1)开启GTID之后,没法使用sql_slave_skip_counter跳过事务。前面介绍过了,使用GTID找点儿时,主库会把从库缺失的GTID,发送给从库,因此skip是没有用的。为了提早发现问题,MySQL在gtid模式下,直接禁止使用set global sql_slave_skip_counter = x。正确的作法是,经过set grid_next= 'zzzz'('zzzz'为待跳过的事务),而后执行BIGIN;COMMIT产生一个空事务,占据这个GTID,再START SLAVE,会发现下一条事务的GTID已经执行过,就会跳过这个事务了
2)若是一个GTID已经执行过,再遇到重复的GTID,从库会直接跳过,可看做GTID执行的幂等性。
3)使用限制:https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-restrictions.html
业界经验:
1. https://code.facebook.com/posts/1542273532669494/lessons-from-deploying-mysql-gtid-at-scale/
2. https://www.percona.com/blog/2015/02/10/online-gtid-rollout-now-available-percona-server-5-6/