GreatSQL社区

搜索

李努力

浅谈GTID及简单测试

李努力 已有 1253 次阅读2022-8-29 09:42 |个人分类:MySQL|系统分类:运维实战

简单介绍一下GTID,并有部分相关实验,技术有限介绍的比较浅显,文中有问题欢迎指正。

GTID相信大家都不陌生,GTID的英文全称为Global Transaction Identifier,在MySQL主从架构中应用广泛。

GTID是由“UUID:事务号“组成的,GTID是基于事务的,在主从架构中,在主库每提交一个事务都会对应生成一个GTID号,GTID支持语句和行格式的复制,而且在主库提交的事务只会在从库应用一次,保证了一致性。

一、下面说一下GTID的优缺点:

1、GTID优点:

①复制安全性高,搭建主从相比与传统的(基于binlog和position号)复制要简单;

②GTID在主库是连续的,保证了数据的一致性;

③故障切换时间更短,降低服务故障时间等。

2、GTID缺点:

①主从表存储引擎必须一致,不能一个innodb一个myisam,这样会导致主备数据不一致,因为GTID是和事务之间一一对应的;

②不允许一个sql同时更新事务引擎表和非事务引擎表;

③在主从模式下,需要主库从库同时开启和关闭gtid;

④不支持create table .... as select 语句,在主库会直接报错的;

⑤不支持create/drop temporary table语句;

⑥不支持传统的复制模式,跳过错误的语句(sql_slave_skip_counter)。

二、GTID部分相关参数介绍(参考官方文档,以及自己总结):

①gtid_mode:控制是否启用gtid,on/on_permissiv/off_perissiv/off;

②enforce_gtid_consistency:用于保证GTID一致性的,有on/off/warn三个值

   on:不允许任何事务违反gtid一致性;

   off:允许所有事务违反gtid一致性;

   warn:允许所有事务违反gtid一致性,但是这种情况下会生成告警,mysql5.7.6新增。

③gtid_executed:它是一个gtid的范围,表示的是已经执行过的所有的gtid事务集或者是由于语句人为设置的gtid。

④gtid_purged:表示的是已经执行过但是已经被purge掉的gtid,也是一个范围,它是executed的一个子集。在以下几种情况下,gtid_purged会有值:

   禁用二进制日志的情况下,提交事务的GTID;

   写入二进制日志的GTID已经被删除了;

   使用语句显示的指定gtid purged:set @@global.gtid_purged=...。

⑤gtid_next:是会话级别的变量,对于提交的事务,会自动分配新的gtid,默认值为automatic,也可以显示指定gtid_next,来指定下一个事务的GTID号。

⑥gtid_owned:该变量主要提供内部使用,保存的是服务器上当前正在使用的所有GTID列表,以及拥有它的线程的ID。

三、进行几个GTID的测试:

测试环境,已经开启GTID复制模式,搭建过程省略。

1、1236报错的情况:

情况1:

主库真正的purge了正在使用的binlog,或者执行了reset master;,这种情况不用过多解释,从库肯定不能找到主库的binlog而报错了。

情况2:

从库gtid不连续,出现了空洞。

1) 如何模拟空洞的出现:

①在从库参数文件中指定slave_skip_errors=1050;

②在从库test库中创建一张表;

③在主库test库中创建同样的一张表,此时如果没有skip参数的话,从库肯定会报错;

④此时在主库向该表中插入一条数据,此时从库gtid就出现了空洞;

⑤查看show slave status\G;

Executed_Gtid_Set: 9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1737:1739-1740,

b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14

对于gtid空洞的情况,假如在需要进行维护的时候,需要重新进行执行change master to语句,可能会出现1236报错,模拟如下:

2)前期准备:

①切换和删除binlog:

由于测试库没有什么业务,binlog没有切换,为了模拟生产上binlog的情况,我们手动进行binlog切换和删除。

mysql> flush logs;   //首先刷新一个日志,这样从库也就指向了新的binlog

Query OK, 0 rows affected (0.04 sec)

mysql> show binary logs;  //查看存在的binlog

+------------------+-----------+

| Log_name         | File_size |

+------------------+-----------+

| mysql-bin.000022 |      2216 |

| mysql-bin.000023 |       194 |

+------------------+-----------+

2 rows in set (0.01 sec)

mysql> purge binary logs to 'mysql-bin.000023';  //将该binlog之前的binlog全部purge掉,这样也就     模拟了主库binlog超过了expire log days时间后对binlog进行的清理,这样binlog里面之前的gtid对应的操作都不存在了。

mysql> show binary logs;  //再次查看只留下了最新的binlog

+------------------+-----------+

| Log_name         | File_size |

+------------------+-----------+

| mysql-bin.000023 |       194 |

+------------------+-----------+

1 row in set (0.00 sec)

3) 复现1236报错:

此时主库老的gtid对应的操作,在binlog中已经被purge了,而从库由于skip参数已经出现了gtid空洞,此时简单的stop slave;start slave;是不会有什么问题的。

查看gtid相关信息:

mysql> show global variables like '%gtid%';

gtid_executed:9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1737:1739-1740,

b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14 //这个值是和show slave status对应的。

gtid_purged :9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1732,

b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-11  //由于从库的binlog没有purge,所以它的值没有变化。

如果此时由于某种原因,我们需要重新执行change master to...操作,此时就会报错1236:

mysql> stop slave;

mysql> change master to .....

mysql> start slave;

mysql> show slave status\G;

Last_IO_Errno: 1236

Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires.'

这是因为:当MASTER_AUTO_POSITION = 1时,在初始连接握手时也就是执行change的时候,从库发送一个GTID集,其中包含它已经接收或提交的事务,或同时包含这两种事务(也就是executed的值)。主库的响应方式是发送所有记录在二进制日志中的事务,这些事务的GTID不在副本发送的GTID集合中。这种交换确保主库只发送从库还没有记录或提交的GTID的事务。

有了上面的理解就不难明白:从库发送的gtid集为:9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1737:1739-1740,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14,是不连续的,缺少9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1738,而主库会发送到从库的是不包含在从库发送出来的gtid的,所以从库还会去主库找9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1738这个gtid,而该gtid所在的binlog已经被purge掉了,所以会报1236的错误。

4) 修复:

我们需要在从库执行purge,将gtid空洞补齐:

mysql> stop slave;

mysql> reset master;

mysql> set global gtid_purged='9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1740,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14';

mysql> reset slave all;

mysql> change master to ....;

mysql> start slave;

mysql> show slave status\G;

Slave_IO_Running: Yes

Slave_SQL_Running: Yes

注意:在执行set global gtid_purged的时候,gtid_purged需要为空,我们需要执行reset master将从库的gtid_purged清空,这样gtid就会连续了,从库发送gtid集的时候就包含了空洞这部分gtid,所以主库也就不会向从库发送该gtid,从库也就不会去主库找该gtid,也就会从最新的gtid去拉取,这样就不会出现1236报错了。

当然还有其他情况会出现1236的报错,比如主库中包含其他实例的gtid(可能是该主库是从其他实例上摘下来的从库),也就是说主库的gtid集包含没在该架构中的实例的gtid,而从库不包含该实例的gtid,当从库执行change的时候大概率也会有1236报错。大概就是这一个意思,这里就不做测试了,想要说明的就是从库在执行change的时候它包含的gtid集要和主库的一致,也就是说主库gtid集中有的uuid,从库中也得有,不管该gtid有没有变化有没有用到。

2、从库gtid_purged值什么时候会变化:

情况1:

从库手动执行set global gtid_purged=....;这个上面已经执行过了。

情况2:

禁用二进制日志的情况下或者关闭log_slave_updates参数,提交事务的GTID;这种情况说的是当从库在禁用binlog的情况下,gtid purged该值是都在变化的,因为从库没有binlog了,也就没有purge binlog这一说了。

情况3:

当从库binlog被purge掉的时候。

此时的gtid executed和gtid purged分别为:

gtid_executed :9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1748,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14

gtid_purged:9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1740,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14

需要注意的是这两个值平时是不一样的,executed表示已经接收执行到的gtid,而purged表示已经被purge掉的gtid。

我们先flush一个binlog,然后再purge:

mysql> flush logs;

Query OK, 0 rows affected (0.09 sec)

mysql> show binary logs;

+------------------+-----------+

| Log_name         | File_size |

+------------------+-----------+

| mysql-bin.000001 |      2217 |

| mysql-bin.000002 |       194 |

+------------------+-----------+

2 rows in set (0.00 sec)

mysql> purge binary logs to 'mysql-bin.000002';

Query OK, 0 rows affected (0.11 sec)

mysql> show global variables like '%gtid%';

gtid_executed:9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1748,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14

gtid_purged :9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1748,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14

可以看到gtid_purged的值发生了变化,包含的是我们已经purge掉的gtid,因为测试库没有业务,所以executed和purged在这里显示出来是一样的。

3、跳过gtid:

当从库出现主键冲突,或者其他报错的时候,我们根据实际情况来进行手动跳过gtid,步骤如下:

mysql> stop slave;

mysql> set gtid_next=...; //该gtid号可以根据报错提示去找。

mysql> begin;commit;

mysql> set gtid_next='automatic';

mysql> start slave;

这样也就解释了gtid_next可以显示的指定,默认情况下是automatic的。

需要注意的是,在生产上从库出现gtid问题的时候,不要一昧的去跳过,需要找到问题的原因,是因为从库没有开启read only还是什么原因,要搞清楚,否则可能造成主从数据不一致。

4、gtid_owned什么时候显示:

在平时使用过程中该值是不容易被发现,被捕捉的。这里我们模拟一个大事务来看看:

在主库执行,模拟大事务:

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> update d set id=sleep(20) + 111 where id=1;  //这里d是一个测试表,可以自行创建。

Query OK, 1 row affected, 1 warning (20.09 sec)

Rows matched: 1  Changed: 1  Warnings: 1

mysql> commit;

Query OK, 0 rows affected (0.02 sec)

在从库查看:

mysql> show global variables like '%gtid%';

gtid_owned  :9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1752#5

它是由gtid和线程组成,我们可以使用show processlist,查看线程5:

mysql> show processlist;

| 5 | system user |           | test | Connect |   34 | User sleep                                             | update d set id=sleep(20) + 111 where id=1 |

正式主库同步过来的操作。

5、gtid_executed表简介:

仅当gtid_mode设置为ON或ON_PERMISSIVE时,GTID才存储在gtid_executed表中。如果从库禁用了binlog或者log_slave_updates=0的时候,该表中存储所有的gtid。

当启用二进制日志的时候,该表并不保存所有的gtid,而是通过show global variables查出来的gtid_executed保存,刷新二进制日志或者重启时,将会把当前binlog中所有的gtid写入到该表中。因此该表有时候可能并不是完整的gtid,所以通过show查看出来的才是完整的。

评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 立即注册

合作电话:010-64087828

社区邮箱:greatsql@greatdb.com

社区公众号
社区小助手
QQ群
GMT+8, 2024-4-20 10:37 , Processed in 0.014405 second(s), 8 queries , Redis On.
返回顶部