MySQL GTID简介
GTID( Global Transaction Identifier)全局事务标识,由主库上生成的与事务绑定的唯一标识,这个标识不仅在主库上是唯一的,在MySQL集群内也是唯一的。GTID是 MySQL 5.6 版本引入的一个有关于主从复制的重大改进,相对于之前版本基于Binlog文件+Position的主从复制,基于GTID的主从复制,数据一致性更高,主从数据复制更健壮,主从切换、故障切换不易出错,很少需要人为介入处理。
MySQL GTID特点
事务提交产生GTID,GTID与事务及事务提交所在的节点绑定,GTID与事务一起写入Binlog,但是从库应用Binlog并不会生成新的GTID。集群中的任何一个节点,根据其GTID值就可以知道哪些事务已经执行,哪些事务没有执行,如果发现某个GTID已执行,重复执行该GTID,将会被忽略,即同一个GTID只能被应用一次。当一个连接执行一个特定GTID的事务,但是还没有提交,此时有另外一个连接也要执行相同GTID的事务,那么第二个连接的执行将会被阻塞,直到第一个事务提交或者回滚。如果第一个事务成功提交,第二个事务将会被忽略。如果第一个事务回滚,第二个事务正常执行。如何开启GTID
gtid_mode=ON
enforce_gtid_consistency=ON
GTID长啥样
GTID = server_uuid:transaction_id
示例:3E11FA47-71CA-11E1-9E33-C80AA9429562:1
server_uuid标识了该事务执行的源节点,存储在数据目录中的auto.cnf文件中,transaction_id 是在该主库上生成的事务序列号,从1开始,示例中 3E11FA47-71CA-11E1-9E33-C80AA9429562 是这个节点的server_uuid,1为这个节点上提交的第1个事务的事务号,如果提交了10个事务,GTID会是这样: 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-10
GTID可以是一段连续或者不连续的几段事务序列集合,下面是可能出现的GTID模样:
3E11FA47-71CA-11E1-9E33-C80AA9429562:1
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-3:12:47-49
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-3, 24DA167-0C0C-11E8-8442-00059A3C7B00:1-19(多个节点提交事务,通常发生了主从切换)
GTID存储在什么地方?
GTID与事务绑定在一起,随着事务的提交,GTID随事务信息一起写入Binlog,通过主从复制,传递到从库。对于已经执行了的事务,其GTID通常会记录在MySQL的系统变量@@GLOBAL.gtid_executed 以及系统表mysql.gtid_executed中,系统变量@@GLOBAL.gtid_executed 在内存中,属于非持久化存储,而系统表mysql.gtid_executed属于持久化存储。
mysql.gtid_executed 表的更新。与Binlog有没有打开有关。
如果开启了Binlog,只有在 Binlog轮转或者MySQL关闭的时候,才会把Binlog中的GTID与入到mysql.gtid_executed表中。
如果没有开启Binlog,那么将通过 gtid_executed_compression_period 这个参数控制mysql.gtid_executed表的更新。默认值为1000,即每1000个事务,将mysql.gtid_executed表中的数据进行合并。 gtid_executed_compression_period设置为0,将不会进行合并,mysql.gtid_executed 表会变得越来越大,直到把磁盘用完。如果开启了Binlog,gtid_executed_compression_period这个参数将不再起作用。mysql.gtid_executed表的数据合并压缩由线程函数compress_gtid_table来执行,位于源码sql/rpl_gtid_persist.cc
extern "C" void *compress_gtid_table(void *p_thd);
该线程不在show processlist中展示,但是可以在 performance_schema.threads 表中看到。
SELECT * FROM performance_schema.threads WHERE NAME LIKE '%gtid%'\G
如果MySQL异常崩溃,GTID没来得及写入mysql.gtid_executed表中,那么在MySQL重新启动后,会从Binlog中搜索GTID,并将这一部分没有写入到mysql.gtid_executed表的GTID写入到表中。如果想查询最新的GTID提交情况,建议查询MySQL全局变量 @@GLOBAL.gtid_executed,而不是查询表 mysql.gtid_executed。
开始GTID后,Binlog存储事务和GTID信息,可以通过mysqlbinlog工具来解析,具体用法如下:
mysqlbinlog --start-datetime="2017-03-21 10:20:00" --start-datetime="2017-03-21 12:20:00" mysql-bin.000001 --base64-output=decode-rows -vvv > binlog.txt
GTID生命周期:
当一个事务在一个主库上被执行和提交,那么这个事务就会被分配一个和该主库uuid相关联的gtid,这个gtid被写入到主库的binlog文件中。当这个binlog文件达到最大值发生轮转,或者MySQL Server关闭时,上一个binlog文件中的事务GTID将会被写入到mysql.gtid_executed表中。事务提交时,该事务的gtid会很快的添加到系统变量 @@GLOBAL.gtid_executed,但是系统表 mysql.gtid_executed 则不会,因为有部分gtid还在binlog中,需要等到binlog轮转或者MySQL Server关闭时才会写入到mysql.gtid_executed表中。主库上的binlog通过主从复制协议传送到从库,并写入到从库的relay log,从库读取relay log中的gtid和对应的事务信息,把gtid_next设置为该gtid值,使得从库使用该gtid值应用其对应的事务。如果多个线程并发地应用同一个事物,比如多个线程设置gtid_next为同一个值,MySQL Server只允许其中一个线程执行,gtid_owned系统变量记录着谁拥有该GTID。GTID Auto-Position:
GTID之前的主从复制是基于文件+偏移的方式,建立主从复制,必须先知道主库的binlog文件和偏移位置( MASTER_LOG_FILE 和 MASTER_LOG_POS)。而使用基于GTID的主从复制,设置 MASTER_AUTO_POSITION =1,从库发送自身已经接收到的gtid给主库,主库将从库缺失的gtid及其对应的binlog文件发送给从库,也就是主库只发送从库没有接收到的事务。所有的信息由MySQL集群自动获取完成,不需要人为干预,大大简化了复制搭建过程。
如果主库要发送给从库的GTID所在的binlog已经被清除了,或者这些gtid已经被添加到gtid_purged,那么主库将发送错误信息给从库,复制将会中断。通常发生这种情况时,从库可以更换复制源,或者使用最新的备份来重建复制。也可以考虑修改增加主库binlog文件的过期时间来减少这种情况的发生。
如果从库已经接收到的gtid 比主库的gtid要多,那么主库也将发送错误信息给从库,同时复制中断。这种情况一般是主库没有设置 sync_binlog=1 ,此时主库发生断电、宕机等故障,导致主库的binlog没有刷到磁盘,而从库已经接收到了主库的binlog。这种情况一般需要人工介入解决,所以推荐更安全的sync_binlog=1 。
基于GTID的复制搭建:
CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=3306,MASTER_USER='repl', MASTER_PASSWORD='123456',MASTER_AUTO_POSITION=1;
基于Binlog+Position的复制搭建:
CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=524;
GTID相关函数:
GTID_SUBSET(set1,set2)
如果set1是set2的子集,返回true,否则返回false
GTID_SUBTRACT(set1,set2)
返回set1与set2的差集,即GTID在set1中,不在set2中。
WAIT_FOR_EXECUTED_GTID_SET(gtid_set[, timeout])
等待GTID执行到某一个位置,如果指定timeout参数,在timeout时间之内,gtid没有执行到该位置 ,则报错返回。
WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS(gtid_set[, timeout][,channel])
等待复制线程(SQL_Thread)执行GTID到某一个位置,同样可以指定超时时间timeout。