最近,遇到了一个有趣的客户案例,其中涉及MariaDB复制设置。我们花了很多时间研究这个问题,并认为在本博文中与您分享这个问题是值得的。
客户环境描述
问题如下:正在使用旧的(10.x之前的版本)MariaDB服务器,并尝试将数据从该服务器迁移到更新的MariaDB复制设置中。这导致使用Mariabackup重建新复制群集中的从站的问题。为了进行测试,我们在以下环境中重新创建了此行为:

数据已使用mysqldump从5.5迁移到10.4:
mysqldump --single-transaction --master-data=2 --events --routines sbtest > /root/dump.sql
这使我们能够收集主二进制日志坐标和一致的转储。结果,我们能够调配MariaDB 10.4主节点并设置旧5.5主节点与新10.4节点之间的复制。流量仍在5.5节点上运行。10.4主服务器正在生成GTID,因为它必须将数据复制到10.4从服务器。在深入研究细节之前,让我们快速看一下GTID在MariaDB中的工作方式。
MariaDB和GTID
首先,MariaDB使用与Oracle MySQL不同的GTID格式。它由三个以短划线分隔的数字组成:
0-1-345
首先是复制域,该域允许正确处理多源复制。这与我们的情况无关,因为所有节点都在同一复制域中。第二个数字是生成GTID的节点的服务器ID。第三个是序列号-它随二进制日志中存储的每个事件单调增加。
MariaDB使用多个变量来存储有关在给定节点上执行的GTID的信息。对我们来说最有趣的是:
Gtid_binlog_pos-根据文档,此变量是写入二进制日志的最后一个事件组的GTID。
Gtid_slave_pos-根据文档,此系统变量包含服务器的从属线程应用于数据库的最后一个事务的GTID。
Gtid_current_pos-根据文档,此系统变量包含最后一次应用于数据库的事务的GTID。如果在相应的GTID的SERVER_ID gtid_binlog_pos等于服务器自己 SERVER_ID,并且序列号是比在相应GTID更高 gtid_slave_pos,然后从GTID gtid_binlog_pos将被使用。否则,来自 gtid_slave_pos的GTID将用于该域。
因此,为了清楚起见,gtid_binlog_pos存储了最后一个本地执行的事件的GTID。Gtid_slave_pos通过从服务器线程和执行事件的存储GTID gtid_current_pos节目或者从值gtid_binlog_pos,如果它具有最高的序列号,它有服务器ID或g tid_slave_pos如果它具有最高的序列。请记住这一点。
问题概述
相关变量的初始状态在10.4主服务器上:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| gtid_binlog_pos | 0-1001-1 |
| gtid_binlog_state | 0-1001-1 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-1 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+----------+
11 rows in set (0.001 sec)
请注意gtid_slave_pos,从理论上讲,这是没有意义的-它来自同一节点,但来自于从属线程。如果您之前进行了主开关,则可能会发生这种情况。我们就是这样做的-在拥有两个10.4节点的情况下,我们将主服务器从服务器ID为1001的主机切换到服务器ID为1002的主机,然后又切换回1001。
之后,我们将复制配置从5.5配置为10.4,结果如下所示:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+-------------------------+
| Variable_name | Value |
+-------------------------+-------------------------+
| gtid_binlog_pos | 0-55-117029 |
| gtid_binlog_state | 0-1001-1537,0-55-117029 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-1 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+-------------------------+
11 rows in set (0.000 sec)
如您所见,从MariaDB 5.5复制的事件都已在gtid_binlog_pos变量中说明:所有事件的服务器ID为55。这导致了严重的问题。您可能还记得,gtid_binlog_pos应该包含在主机上本地执行的事件。在这里,它包含从具有不同服务器ID的另一台服务器复制的事件。
当您要重建10.4从站时,这使事情变得艰难,这就是原因。就像Xtrabackup一样,Mariabackup的工作方式也很简单。它在扫描重做日志并存储所有传入事务时,从MariaDB服务器复制文件。复制文件后,Mariabackup将使用带有读取锁的FLUSH TABLES或备份锁来冻结数据库,具体取决于MariaDB版本和备份锁的可用性。然后,它读取最新执行的GTID并将其与备份一起存储。然后释放锁并完成备份。备份中存储的GTID应该用作节点上最新执行的GTID。如果重建从站,它将被放置为gtid_slave_pos,然后用于启动GTID复制。该GTID来自gtid_current_pos,这很有意义-毕竟是“应用于数据库的最后一笔交易的GTID”。敏锐的读者已经可以看到问题所在。让我们展示从5.5母版复制10.4时变量的输出:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+-------------------------+
| Variable_name | Value |
+-------------------------+-------------------------+
| gtid_binlog_pos | 0-55-117029 |
| gtid_binlog_state | 0-1001-1537,0-55-117029 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-1 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+-------------------------+
11 rows in set (0.000 sec)
Gtid_current_pos设置为0-1001-1。这绝对不是正确的时间,它取自gtid_slave_pos,而之后有大量来自5.5的交易。问题是那些事务存储为gtid_binlog_pos。另一方面,计算gtid_current_pos的方式是:在gitd_binlog_pos中需要GTID的本地服务器ID 才能用作gtid_current_pos。在我们的例子中,它们具有5.5节点的服务器ID,因此它们不会被正确地视为在10.4主服务器上执行的事件。备份还原后,如果根据备份中存储的GTID状态设置从属,则最终将重新应用来自5.5的所有事件。显然,这将破坏复制。
解决方案
解决此问题的方法是采取其他一些步骤:
- 将复制从5.5停止到10.4。在10.4主服务器上运行STOP SLAVE
- 在10.4上执行任何交易-如果不存在,请创建模式错误修正-这将改变GTID的情况,如下所示:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+---------------------------+
| Variable_name | Value |
+-------------------------+---------------------------+
| gtid_binlog_pos | 0-1001-117122 |
| gtid_binlog_state | 0-55-117121,0-1001-117122 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-117122 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+---------------------------+
11 rows in set (0.001 sec)
最新的GITD是在本地执行的,因此将其存储为gtid_binlog_pos。由于具有本地服务器ID,因此将其选择为gtid_current_pos。现在,您可以备份并使用它来从10.4主服务器上重建从服务器。完成此操作后,再次启动从属线程。
MariaDB知道存在这种错误,不幸的是到目前为止尚未修复。我们发现,此问题影响MariaDB最高为5.5。来自MariaDB 10.0的非GTID事件在10.4上正确地说明为来自从属线程,并且gtid_slave_pos已正确更新。MariaDB 5.5是一个很旧的版本(即使仍受支持),因此您可能仍会看到安装程序正在运行,并尝试从5.5迁移到支持GTID的最新MariaDB版本。更糟糕的是,根据我们发现的错误报告,这也影响了从非MariaDB服务器(Percona Server 5.6上出现的评论提及问题之一)到MariaDB的复制。
无论如何,希望此文章对您有用,并且希望您不会遇到我们刚刚描述的问题。
来源:处理从非GTID到GTID MariaDB数据库集群的复制问题
https://severalnines.com/database-blog/handling-replication-issues-non-gtid-gtid-mariadb-database-clusters




