gtid知识:
一、gtid持久化介质
01 、mysql.gtid_executed表:
work@master (mysql) > show create table gtid_executedG; *************************** 1. row *************************** Table: gtid_executed Create Table: CREATE TABLE `gtid_executed` ( `source_uuid` char(36) NOT NULL COMMENT 'uuid of the source where the transaction was originally executed.', `interval_start` bigint(20) NOT NULL COMMENT 'First number of interval.', `interval_end` bigint(20) NOT NULL COMMENT 'Last number of interval.', PRIMARY KEY (`source_uuid`,`interval_start`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 STATS_PERSISTENT=0 1 row in set (0.07 sec) ERROR: No query specified Mon May 11 16:56:12 2020
02、binlog中的gtid_event
# 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 /*!*/;
二、gtid相关变量和表
01、mysql.gtid_executed表
含义:数据库已经执行了哪些Gtid事物,存储在表中。只有从库在binlog或log_slave_updates至少一个关闭时,才会实时更新的。
1. 执行 reset master 时,则删除该表所有记录。 2. 执行 set global gitd_purged='' 时,则插入对应的记录到mysql.gtid_executed表,且同时更新gtid_executed为和gtid_purged相同的值,即始终保持gtid_purged是gtid_executed的子集。 3. 主库:若binlog关闭,则不更新;若binlog打开,则binlog发生rotate切换时,会更新该表数据。 4. 从库:若binlog或log_slave_updates至少一个关闭,则实时更新;若binlog和log_slave_updates都开启,则binlog发生rotate切换时,会更新该表数据。
02、gtid_executed变量
含义:数据库已经执行了哪些Gtid事物,存储在内存中。
show slave status中的Executed_Gtid_Set也取自这里。
gtid_executed为空有两种情况:一是执行reset master;二是之前没有启动过基于GTID的复制。
1. 执行 reset master 时,则该变量设置为空。 2. 执行 set global gitd_purged='' 时,则插入对应的记录到mysql.gtid_executed表,且同时更新gtid_executed为和gtid_purged相同的值,即始终保持gtid_purged是gtid_executed的子集。 3. mysql启动时,通过读取mysql.gtid_executed表的数据来初始化gtid_executed变量。 4. 只有当主库在关闭binlog的时候,不会更新gtid_executed变量;其余情况,都会实时更新gtid_executed变量。
03、gitd_purged变量
含义:全局变量。用于记录已经被清除了的binlog事务集合,gitd_purged变量是gtid_executed变量的子集。
只有gtid_executed为空时,才能手动设置gitd_purged变量。
1. 执行 reset master 时,则该变量设置为空。 2. 执行 set global gitd_purged='XXX' 时,则插入对应的记录到mysql.gtid_executed表,且同时更新gtid_executed为和gtid_purged相同的值,即始终保持gtid_purged是gtid_executed的子集。 用来来提示Mysql,哪些Gtid事务我已经执行过了。用于搭建从库时,使用极多。通常是先在从库执行:reset master;然后再在从库执行:set global gitd_purged='XXX', 注意,这里的XXX需要结合该从库Retrieved_Gtid_Set和Executed_Gtid_Set的并集,还要加上主库的Executed_Gtid_Set中存在的,但不是该主库产生的gtid,且该从库没有的gtid。 但是这样有个隐患,就是主库上有其他机器执行过的gtid而得到的数据,但是该从库却没有真正存在这些数据,而你在该从库上却设置为这些gtid事务是自己purge掉了,其实自己压根没有这些数据,这样就导致从库的数据少于主库,即丢掉了数据。 3. mysql启动时,通过读取mysql.gtid_executed表的数据来初始化gtid_executed变量。 4. 主库:若binlog关闭,则不更新gitd_purged变量;若binlog打开,则binlog被清理时,会更新gitd_purged变量。 5. 从库:若binlog或log_slave_updates至少一个关闭,则实时更新gitd_purged变量;若binlog和log_slave_updates都开启,则binlog被清理时,会更新gitd_purged变量。 ######################################################################################################################################################################################## 手动删除binlog: show binary logs; purge binary logs to 'mysql-bin.000046'; purge binary logs before '2020-10-10 10:10:10'; 自动删除binlog: expire_logs_days时间到了,执行binlog删除
跳过gtid事务:
(1)停止复制:
stop slave;
(2)重置master:
reset master;
(3)设置gtid_purged变量:
set global gtid_purged='XXX';
(4)开启复制:
start slave;
3,使用空事务处理故障 情况1: 主键重复 # Last_Error: Could not execute Write_rows event on table test.a; Duplicate entry '500' for key 'PRIMARY', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log mysql.000005, end_log_pos 651107 # 方法1: # 注入空事务 Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: 7abe211e-fb4f-11e7-b16e-000c29ba88ca:66253-68381:69091-70362 Executed_Gtid_Set: 7abe211e-fb4f-11e7-b16e-000c29ba88ca:1-70362, 9a455b69-fb4f-11e7-b993-000c29e8a43c:1 ########################## ########################## (root@Slave)[tempdb]>stop slave sql_thread; (root@Slave)[tempdb]>set gtid_next='7abe211e-fb4f-11e7-b16e-000c29ba88ca:70363'; (root@Slave)[tempdb]>begin;commit; (root@Slave)[tempdb]>set gtid_next='AUTOMATIC'; (root@Slave)[tempdb]>start slave sql_thread; (root@Slave)[tempdb]>show slave status G
04、Executed_Gtid_Set
含义:
1. 在主库中,执行 show master status命令,所得Executed_Gtid_Set,就是和全局变量gitd_executed相同值,任何时候,两者完全相同值。 2. 在从库中,执行 show slave status命令,所得Executed_Gtid_Set,就是和全局变量gitd_executed相同值,任何时候,两者完全相同值。
05、Retrieved_Gtid_Set
含义:仅在从库中才有,在主库中不存在。
06、Previous-GTIDs
1. Previous_gtid_log_event在每个binlog 头部都会有每次binlog rotate的时候存储在binlog头部,Previous-GTIDs在binlog中只会存储在这台机器上执行过的所有binlog,不包括手动设置gtid_purged值。
换句话说,如果你手动set global gtid_purged=xx; 那么xx是不会记录在Previous_gtid_log_event中的。
07、gtid_next变量
跳过gtid事务:
(1) 停止slave进程:
mysql> stop slave; (2) 设置事务号,事务号从Retrieved_Gtid_Set获取: 在session里设置gtid_next,即跳过这个GTID mysql> SET @@sission.gtid_next= '8f9e146f-0a18-11e7-810a-0050568833c8:4'
(3)设置空事物 mysql> begin; commit;
(4)恢复事物号 mysql> set session gtid_next= automatic; (5) 启动slave进程 mysql> start slave;
08、gtid_owned
08、gtid_mode
09、enforce_gtid_consistency
10、gtid_executed_compression_period
1、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
2、查看配置
> show global variables like 'gtid_%'G; *************************** 1. row *************************** Variable_name: gtid_executed Value: 5a1a41db-9f15-11e9-a991-e4434b210720:1-2, 5aa95098-9f15-11e9-98f3-e4434b5a47f8:1-429, 5bcba8f5-9f15-11e9-9b14-e4434b210748:1-6319147104, 5d2b585f-9f15-11e9-a58d-e4434b2106e8:1-1373519, 62cdce4f-9f15-11e9-9f0d-e4434b21b430:1-2, 66b80dbd-c979-11e9-8582-246e96c58570:1, 85e1f282-f3a7-11e9-a222-e4434b2106e8:1-24 *************************** 2. row *************************** Variable_name: gtid_executed_compression_period Value: 1000 *************************** 3. row *************************** Variable_name: gtid_mode Value: ON *************************** 4. row *************************** Variable_name: gtid_owned Value: *************************** 5. row *************************** Variable_name: gtid_purged Value: 5a1a41db-9f15-11e9-a991-e4434b210720:1-2, 5aa95098-9f15-11e9-98f3-e4434b5a47f8:1-429, 5bcba8f5-9f15-11e9-9b14-e4434b210748:1-6047460531:6047460533, 5d2b585f-9f15-11e9-a58d-e4434b2106e8:1-1373519, 62cdce4f-9f15-11e9-9f0d-e4434b21b430:1-2, 66b80dbd-c979-11e9-8582-246e96c58570:1, 85e1f282-f3a7-11e9-a222-e4434b2106e8:1-22 5 rows in set (0.00 sec) ERROR: No query specified Sun Dec 8 16:57:49 2019
这里有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。
binlog文件中的gtid
# 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线程中断。
主库发送的二进制的事件:主库Executed_Gtid_Set对从库(Retrieved_Gtid_Set U Executed_Gtid_Set)的补集
考虑下面这种情况,有个集群已经在使用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集合一致。