MySQL高可用实践
上QQ阅读APP看书,第一时间看更新

3.7 GTID集合运算函数

3.7.1 GTID内置函数

MySQL 8包含GTID_SUBSET、GTID_SUBTRACT、WAIT_FOR_EXECUTED_GTID_SET和WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS这4个内置函数,用于GTID集合的基本运算。

  • GTID_SUBSET(set1,set2):给定两个GTID集set1和set2,set1是set2的子集返回true,否则返回false。
  • GTID_SUBTRACT(set1,set2):给定两个GTID集set1和set2,返回set1与set2的差集。
  • WAIT_FOR_EXECUTED_GTID_SET(gtid_set[, timeout]):等到服务器应用了包含在gtid_set中的所有事务。如果指定可选的timeout值(秒数),超时会使函数停止等待而退出。
  • WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS(gtid_set[, timeout][,channel]):与WAIT_FOR_EXECUTED_GTID_SET类似,但针对单个启动的复制通道。

3.7.2 用户自定义函数

用户可以在自定义函数中调用这些内置函数,实现一些常用的GTID集合运算,下面是MySQL 8文档中的几个例子。

  • 如果两个GTID集相同,函数返回非零值。
  • 如果两个GTID集不相交,函数返回非零值。
  • 如果两个GTID集不相交,则函数返回非零,sum是两个集的并集。
  • 函数返回格式化的GTID集。没有空格且没有重复,UUID按字母顺序排列,间隔按数字顺序排列。
     create function gtid_normalize(g longtext)
     returns longtext deterministic
     return gtid_subtract(g, '');
  • 函数返回两个GTID集的并集。
  • 函数返回两个GTID集的交集。
  • 函数返回两个GTID集的对称差集。
  • 函数返回除去指定UUID的GTID集。
  • 函数返回指定UUID的GTID集。

3.7.3 使用示例

1. 验证从库的复制是否最新

内置函数GTID_SUBSET和GTID_SUBTRACT可用于检查从库是否应用了主库的每个事务。使用GTID_SUBSET执行此检查,在从库上执行以下命令:

如果返回0,则master_gtid_executed中的某些GTID不存在于slave_gtid_executed中,因此从库不是最新的。要使用GTID_SUBTRACT执行检查,在从库上执行以下命令:

返回master_gtid_executed中但未在slave_gtid_executed中的GTID。如果返回值不为空,则从库不是最新的。

2. 验证mysqldump导出和导入

自定义函数GTID_IS_EQUAL、GTID_IS_DISJOINT和GTID_IS_DISJOINT_UNION可用于验证涉及多个数据库和服务器的备份、还原操作。此示例中,server1包含数据库db1,server2包含数据库db2。目标是将数据库db2复制到server1,server1上的结果应该是两个数据库的并集。过程是使用mysqldump备份server2,然后在server1上恢复此备份。

如果mysqldump的选项--set-gtid-purged设置为ON或默认值为AUTO时,则程序的输出包含SET @@GLOBAL.gtid_purged语句,该语句将server2中的gtid_executed集添加到server1上的gtid_purged集。gtid_purged集包含已在服务器上提交但在服务器上的任何二进制日志文件中不存在的所有事务的GTID。将数据库db2复制到server1时,必须将server2上提交的事务的GTID(不在server1上的二进制日志文件中)添加到server1的gtid_purged集中,以使这些GTID集不在server1上重建。

  • 使用GTID_IS_EQUAL验证备份操作是否为SET @@GLOBAL.gtid_purged语句计算了正确的GTID集。在server2上,从mysqldump输出中提取该语句,并将GTID集存储到本地变量中,例如$gtid_purged_set。然后执行以下语句:
     server2> SELECT GTID_IS_EQUAL($gtid_purged_set, @@GLOBAL.gtid_executed);

如果结果为1,则两个GTID集相等,并且已正确计算该集。

  • 使用GTID_IS_DISJOINT验证mysqldump输出中设置的GTID与server1上的gtid_executed集不重叠。如果存在任何重叠,则在将数据库db2复制到server1时会出现错误。将mysqldump输出中的gtid_purged集提取并存储到如上所述的局部变量中,然后执行以下语句:
     server1> SELECT GTID_IS_DISJOINT($gtid_purged_set, @@GLOBAL.gtid_executed);

如果结果为1,则两个GTID集之间没有重叠,因此不存在重复的GTID。

  • 使用GTID_IS_DISJOINT_UNION验证还原操作是否导致server1上的GTID状态正确。在恢复备份之前,在server1上,通过执行以下语句获取现有的gtid_executed集:
     server1> SELECT @@GLOBAL.gtid_executed;

将结果存储在本地变量$original_gtid_executed中。还将gtid_purged集存储在局部变量中。当server2的备份已恢复到server1上时,执行以下语句以验证GTID状态:

如果结果为1,则已验证来自server1的原始gtid_executed集($original_gtid_executed)和从server2添加的gtid_purged集($gtid_purged_set)没有重叠,并且server1上已更新的gtid_executed集现在包含来自server1的前一个gtid_executed集加上来自server2的gtid_purged集,这是所需的结果。确保在server1上进行任何进一步的事务之前执行此检查,否则gtid_executed集中的新事务将会失败。

3. 手工选择作为新主库的从库

自定义函数GTID_UNION可用于从一组复制从库中识别最新的从库,以便在主库意外停止后执行手动切换。如果某些从库遇到复制延迟,则此函数可用于计算最新的从库,而无需等待所有从库应用完其现有的中继日志,从而最大限度地缩短主从切换时间。该函数可以返回每个从库上的gtid_executed集合与从库接收的事务集合的并集,后者记录在performance_schema.replication_connection_status表中。可以比较这些结果,以查找哪个从库的事务记录是最新的,即使并非所有事务都已提交。

在每个复制从库上,通过发出以下语句来计算完整的事务记录:

然后比较每个从库的结果,选择具有最新事务记录的从库用作新主库。

4. 检查从库上的异常事务

自定义函数GTID_SUBTRACT_UUID可用于检查从库是否只接收到源自其指定主库的事务。对于单个主库的复制,执行以下语句,server_uuid_of_master是主库的server_uuid:

     SELECT GTID_SUBTRACT_UUID(@@GLOBAL.gtid_executed, server_uuid_of_master);

如果结果不为空,则返回的事务为不是源自指定主库的异常事务。对于多主库复制拓扑结构中的从库,重复该功能,例如:

如果结果不为空,则返回的事务是来自非指定主库的异常事务。

5. 验证复制拓扑结构中的服务器是否执行过本地事务

自定义函数GTID_INTERSECTION_WITH_UUID可用于验证服务器是否执行过本地事务。可以在服务器上发出以下语句来检查:

     SELECT GTID_INTERSECTION_WITH_UUID(@@GLOBAL.gtid_executed, my_server_uuid);
6. 在多主复制设置中验证附加从库

假设master1和master2为双主复制,互为主从,同时master2还有自己的从库slave3,如图3-1所示。

图3-1 多主复制

如果master2配置了log_slave_updates=ON,那么slave3也将接收并应用master1的事务,如果master2使用log_slave_updates=OFF,则不会这样做。在这种情况下,自定义函数GTID_INTERSECTION_WITH_UUID可用于标识master2发起的事务,丢弃master2从master1复制的事务。然后可以使用内置函数GTID_SUBSET将结果与slave3上的gtid_executed集进行比较。如果slave3与master2保持同步,则slave3上的gtid_executed设置包含交集中的所有事务(源自master2的事务)。

要执行此检查,可将master2的gtid_executed、master2的server_uuid和slave3的gtid_executed集存储到客户端变量中,例如:

然后使用GTID_INTERSECTION_WITH_UUID和GTID_SUBSET将这些变量作为输入,例如在slave3上执行:

     SELECT GTID_SUBSET(GTID_INTERSECTION_WITH_UUID(
     $master2_gtid_executed,$master2_server_uuid),$slave_gtid_executed);

来自master2的服务器标识符($master2_server_uuid)与GTID_INTERSECTION_WITH_UUID一起使用,以识别并返回源自master2的gtid_executed集合中的那些GTID,省略源自master1的GTID。然后使用GTID_SUBSET将得到的GTID集与从库上所有已执行GTID的集合进行比较。如果此语句返回非零(true),则来自master2的所有已识别的GTID(第一个集输入)也位于从库的gtid_executed集(第二个集输入)中,这意味着从库已复制源自master2的所有事务。