前言:今天听张同学在群里分享了GTID,对我自己而言,也算是一次重新认识GTID的机会,看了张同学的分享,相对来说比较简单精炼,晚上在回家的路上,我就在思考今天的文章写点什么东西,那么我想了想,打算从官网翻译一些关于GTID的文章作为GTID学习的系列文章吧。
- GTID的概念
- GTID sets
- mysql.gtid_executed Table
- mysql.gtid_executed Table Compression
GTID是由源库(主库)每次事务提交产生的唯一标识,这个GTID不仅是单实例的唯一标识,而且在一组主从结构中也是唯一标识,并且所有的GTID和所有的事务都是一对一的关系。
GTID格式如下:
GTID = source_id:transaction_id
sourceid表示源库,通常来说,服务器的server_uuid主要就是为了这个目的。transaction_id是由事务提交的顺序产生的一个连续的数字。例如,第一个提交的事务的事务号就是1,第十个提交的事务产生的事务号就是10,并且,GTID不可能为0.例如,server_uuid为3E11FA47-71CA-11E1-9E33-C80AA9429562的第23个事务的GTID就表示为如下:
3E11FA47-71CA-11E1-9E33-C80AA9429562:23
以上的GTID表示可以在SHOW SLAVE STATUS或者在binlog中看到。可以通过命令mysqlbinlog --base64-output=DECODE-ROWS或者SHOW BINLOG EVENTS解析binlog文件见到。
例如,我们通过SHOW MASTER STATUS或SHOW SLAVE STATUS见到如下格式:
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
这个例子显示server_uuid为3E11FA47-71CA-11E1-9E33-C80AA9429562的第一到第五个事务。这个表达式也用于启动复制START SLAVE时的选项SQL_BEFORE_GTIDS和SQL_AFTER_GTIDS。
- GTID sets
一个GTID集合由如下的全局事务编号来表示:
gtid_set:
uuid_set [, uuid_set] ...
| ''
uuid_set:
uuid:interval[:interval]...
uuid:
hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh
h:
[0-9|A-F]
interval:
n[-n]
(n >= 1)
GTID集在MySQL中有多种用途。例如,.gtid_executed和.gtid_purged系统变量也保存了GTID的值。此外,函数GTID_SUBSET()和GTID_SUBTRACT()也需要GTID作为输入参数。系统变量返回GTID时,UUID的字母和数字是按升序的方式排列的。
GTID通常出现在主从结构中。这就表示我们可以通过解析binlog获取到在从库上应用任何事务的来源。此外,一旦给定的GTID的事务已经在给定的数据库上执行过以后,相同的GTID的事务将不再执行。因此,相同GTID的事务在从库上只能执行一次,这就保证了数据的一致性。
一旦主从结构采用了GTID,我们就不再需要主库的binlogfile名和复制的位置点。所有同步所需要的主库的信息直接从复制的数据流中就可以得到。GTID代替了传统复制所需要的文件偏移量。因此,在change master的时候不需要MASTER_FILE和MASTER_LOG_POS选项,只需要开启MASTER_AUTO_POSITION即可。
GTID的产生由下面几步产生:
1.一个事务在主库上执行并提交。
这个事务由主库的UUID和未在该实例内使用过的最小非零事务序列号标识。GTID写入主库的binlog。
2.binlog传输到从库上以后存入从库的relay log,slave读取GTID然后设置自己的gtid_next的值,这个值告诉slave下一个事务必须使用这个GTID。
提醒:slave 设置gtid_next是一个会话级的变量
3.slave会去确认这个GTID的事务是否已经执行过了并且其他会话也没有读过该GTID,如果这个GTID没有用过,那么slave就会去写GTID,应用该事务然后写改事务到自身的binlog中。简而言之就是,多个客户端不允许同时应用同一个事务。
4.因为.gtid_next是非空,slave不会主动去为某个事务去生成一个GTID,而是把GTID存在.gtid_next这个变量中,也就是说从master获取到GTID,然后写入到该变量中,最后才写binlog。
- mysql.gtid_executed Table
从MySQL5.7.5开始,GTID信息存储在gtid_executed表内(mysql库)。该表的每一行存储了每个GTID的信息,源库的uuid,开始和结束的事务编号。
MySQL安装或升级后,mysql.gtid_executed表会自动创建,建表SQL如下:
CREATE TABLE gtid_executed (
source_uuid CHAR(36) NOT NULL,
interval_start BIGINT(20) NOT NULL,
interval_end BIGINT(20) NOT NULL,
PRIMARY KEY (source_uuid, interval_start)
)
mysql.gtid_executed表允许slave在没有开启binlog的情况下使用GTID,并且即使是binlog丢失的情况,也会保留GTID的历史信息。 只有当GTID_MODE或者ON_PERMISSIVE时GTID才会存储在该表中,至于哪个GTID会存储在表中取决于binlog是否开启。
- 如果binlog关闭,或者log_slave_updates关闭,那么数据库会将GTID连同表中的事务一起存储。此外,该表会以用户配置的频率来压缩该表。这种情况只可能应用在binlog后者slave update logging都关闭的slave上而不会出现在master上,因为master必须开启binlog主从才能复制。
- 如果binlog开启的话。无论binlog被切割或者服务被重启了,mysql会把之前所有写入binlog事务的GTID写入gtid_executed表。这种情况应用于主从复制的主库上,或者开启binlog的从苦苦。
如果服务器意外关闭,当前binlog的GTID没有存储到gtid_executed表,在这种情况下,当mysql在恢复的时候,会将GTID写入该表和gtid_executed变量。 开启binlog时,mysql.gtid_execute表不提供所有已执行事务的GTID的完整记录,而是由全局值gtid_executed记录。mysql.gtid_executed表通过RESET MASTER来重置。 - mysql.gtid_executed Table Compression
随着时间的推移,mysql.gtid_Executed表可以填充许多行,这些行引用来自同一服务器上的单个gtid,其事务ID组成一个序列,类似于这里所示:
mysql> SELECT * FROM mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37 | 37 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 38 | 38 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 39 | 39 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 40 | 40 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 41 | 41 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 42 | 42 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 43 | 43 |
...
如果通过用跨事务标识符整个间隔的单个行替换每一组这样的行来定期压缩表,则可以节省相当大的空间,如下所示:
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37 | 43 |
...
当启用gtids时,服务器会周期性地对mysql.gtid_Executed表执行这种类型的压缩。您可以通过设置已执行的gtids压缩_周期系统变量来控制允许在表压缩之前经过的事务数,从而控制压缩率。此变量的默认值是1000;这意味着默认情况下,表的压缩是在每1000次事务之后执行的。压缩是完全不执行的;但是,如果这样做,您应该准备好可能需要大量增加gtid_Executed表所需的磁盘空间。
mysql.gtid_Executed表的压缩由一个名为线程/sql/compress_gtid_table的专用前台线程执行。这个线程没有列在显示处理列表的输出中,但它可以被看作线程表中的一行,如下所示:
mysql> SELECT * FROM performance_schema.threads WHERE NAME LIKE '%gtid%'\G
*************************** 1. row ***************************
THREAD_ID: 26
NAME: thread/sql/compress_gtid_table
TYPE: FOREGROUND
PROCESSLIST_ID: 1
PROCESSLIST_USER: NULL
PROCESSLIST_HOST: NULL
PROCESSLIST_DB: NULL
PROCESSLIST_COMMAND: Daemon
PROCESSLIST_TIME: 1509
PROCESSLIST_STATE: Suspending
PROCESSLIST_INFO: NULL
PARENT_THREAD_ID: 1
ROLE: NULL
INSTRUMENTED: YES
HISTORY: YES
CONNECTION_TYPE: NULL
THREAD_OS_ID: 18677
线程/sql/compress_gtid_table线程通常处于休眠状态,直到执行了gtids压缩期事务,然后醒来执行前面描述的mysql.gtid_Executed表的压缩。然后,它会休眠,直到发生了另一个执行的gtids压缩期事务,然后醒来再次执行压缩,重复这个循环不确定。当二进制日志记录被禁用时,将这个值设置为0意味着线程总是休眠,永远不会醒来。