目录
上篇解释了许多 GTID 的原理,以及在 MySQL 复制中所起的作用,并且进行了很多实验加以辅助说明。本篇演示如何从头开始一步步配置 GTID 复制。实验环境同“二、复制实验环境”。这里只讨论在联机情况下进行配置,因为相对于空库或脱机等理想情况,联机配置复制的需求更为典型和常见。
一、配置 GTID 复制
1. 联机配置 GTID 复制
依照以前讨论异步复制时得出的结论,本实验使用 XtraBackup 工具进行联机数据备份。主、从服务器已经进行了以下配置:
- 在主库上建立复制专属用户。
- 在主、从库安装 XtraBackup。
- 配置主库到从库的 SSH 免密码连接。
- 停止作为从库的 MySQL 实例,并清空其数据目录。
这些作为配置 MySQL 复制的前置步骤,具体操作参考“(2)使用 XtraBackup 工具”。现在说明联机配置 GTID 复制的步骤。
(1)检查主库中是否有不支持 GTID 的操作
set global enforce_gtid_consistency=warn;
让服务器在正常工作负载下运行一段时间并监控错误日志,最好包含一天负载最高的时间段,有条件建议观察 2-3 天。如果此步骤导致错误日志中出现任何警告,需要调整应用程序,使其仅使用与 GTID 兼容的功能,并且不能生成与 GTID 相关的任何警告。这是一个重要步骤,在进行下一步之前,必须确保错误日志中未生成警告。
(2)在主库联机设置 GTID 相关参数
set global enforce_gtid_consistency=true;
set global gtid_mode=off_permissive;
set global gtid_mode=on_permissive;
set global gtid_mode=on;
enforce-gtid-consistency 启用后,MySQL 服务器通过仅允许执行使 GTID 安全的语句来强制 GTID 一致性。在启用基于 GTID 的复制之前,必须将此选项设置为 true。enforce_gtid_consistency 的可配置值为:
- false:允许事务违反 GTID 一致性。
- true:不允许事务违反 GTID 一致性。
- warn:允许事务违反 GTID 一致性,但在这种情况下会生成警告。
当 enforce_gtid_consistency 设置为 true 时,只能使用 GTID 安全的语句,例如如下操作不能与此选项一起使用:
- CREATE TABLE ... SELECT 语句。
- 在事务内创建 TEMPORARY TABLE 或 DROP TEMPORARY TABLE 语句。
- 更新事务和非事务表的事务或语句。
enforce_gtid_consistency 仅在语句进行二进制日志记录时生效。如果在服务器上禁用了二进制日志记录,或者由于过滤器删除了语句而未将语句写入二进制日志,则不会对未记录的语句检查或强制执行 GTID 一致性。
在包含 gtid_mode 系统变量的所有 MySQL 版本中,它都可以设置成 on 或 off。MySQL 5.7.6 之后 gtid_mode 提供了两个新的选项分别为 on_permissive 和 off_permissive。当 gtid_mode = on 时,无法复制匿名事务,而当 gtid_mode = off 时,只能复制匿名事务。当 gtid_mode = off_permissive 时,新事务是匿名的,同时允许复制的事务是 GTID 或匿名事务。当 gtid_mode = on_permissive 时,新事务使用 GTID,同时允许复制事务为 GTID 或匿名事务。这意味着可以拥有一个复制拓扑,其中包含使用匿名和 GTID 事务的服务器。例如,具有 gtid_mode = on 的主库可以有使用 gtid_mode = on_permissive 从库。gtid_mode 在主从库上的兼容性以及能否使用自动定位如下表所示,每个条目的含义如下:
- Y:主库和从库的 gtid_mode 兼容
- N:主库和从库的 gtid_mode 不兼容
- *:自动定位可与此组合一起使用
联机设置 gtid_mode 时,只能基于 OFF、OFF_PERMISSIVE、ON_PERMISSIVE、ON 顺序一次改变一步。例如,如果 gtid_mode 当前设置为 OFF_PERMISSIVE,则可以更改为 OFF 或ON_PERMISSIVE,但不能直接更改为 ON,否则会报以下错误:
ERROR 1788 (HY000): The value of @@GLOBAL.GTID_MODE can only be changed one step at a time: OFF <-> OFF_PERMISSIVE <-> ON_PERMISSIVE <-> ON. Also note that this value must be stepped up or down simultaneously on all servers. See the Manual for instructions.
这样实现是为了确保服务器能够正确处理从匿名事务更改为 GTID 事务的过程,此过程中可能同时包含这两种模式的事务。在 gtid_mode = on 和 gtid_mode = off 之间切换时,GTID 状态(也就是 gtid_executed 的值)是持久化的,因此不管 gtid_mode 的类型如何更改,都可确保始终保留服务器应用的 GTID 集。无论当前选择的 gtid_mode 如何,与 GTID 相关的字段都会显示正确的信息。显示 GTID 集的字段(例如 replication_connection_status 性能架构表中的 gtid_executed、gtid_purged、RECEIVED_TRANSACTION_SET 以及 SHOW SLAVE STATUS 的 GTID 相关结果)在没有 GTID 时返回空字符串。显示单个 GTID 的字段(如 Performance Schema replication_applier_status_by_worker 表中的 CURRENT_TRANSACTION)在未使用 GTID 事务时显示 ANONYMOUS。从库使用 gtid_mode = on 复制提供了自动定位的功能。
当前选择的 gtid_mode 也会影响 gtid_next 变量。下表显示了服务器对 gtid_mode 和 gtid_next 不同值的行为。每个条目的含义如下:
- ANONYMOUS:生成匿名事务。
- Error:生成错误并且无法执行 SET GTID_NEXT。
- UUID:NUMBER:使用指定的 UUID:NUMBER 生成 GTID。
- New GTID:使用自动生成的数字生成 GTID。
当二进制日志关闭且 gtid_next 设置为 AUTOMATIC 时,不会生成 GTID,这与先前版本的行为一致。注意,为了保证主从数据一致性和实例恢复的性能,在 MySQL 8 中作为一项基本原则,除非有特殊需求,与复制相关的其它系统变量最好保持缺省值,包括但不限于下面所列出的系统变量:
- autocommit = ON
- log_bin = ON
- log_slave_updates = ON
- innodb_flush_log_at_trx_commit = 1
- sync_binlog = 1
- master_info_repository = TABLE
- relay_log_info_repository = TABLE
- relay_log_recovery = OFF
- binlog_gtid_simple_recovery = ON
- innodb_replication_delay = 0
(3)用 xtrabackup 备份并传输
xtrabackup -uroot -p123456 --socket=/tmp/mysql.sock --no-lock --backup --compress --stream=xbstream --parallel=4 --target-dir=./ | ssh mysql@172.16.1.126 "xbstream -x -C /usr/local/mysql/data/ --decompress"
使用 xtrabackup 的一个好处是,不必考虑从 gtid_mode=off 到 gtid_mode=on 的过程中是否存在正在进行的匿名事务。xtrabackup 执行的是物理备份,主库上无论是匿名事务还是 GTID 事务,最终数据文件和二进制日志文件都会被拷贝到从库,并且在恢复备份、启动实例和复制后,可以使用 GTID 的自动定位功能找到初始复制点。
(4)在从库恢复备份
xtrabackup --prepare --target-dir=/usr/local/mysql/data/
(5)在从库的配置文件中添加以下选项
server_id=1126 # 服务器ID
read_only=on # 从库只读
gtid_mode=on # 开启GTID
enforce-gtid-consistency=true # 强制GTID一致
(6)启动从库
mysqld_safe --defaults-file=/etc/my.cnf &
(7)在从库启动复制
change master to
master_host = '172.16.1.125',
master_port = 3306,
master_user = 'repl',
master_password = '123456',
master_auto_position = 1;
start slave;
show slave status\G
可以在 slave status 中看到复制正在进行,Retrieved_Gtid_Set 和 Executed_Gtid_Set 不断增加,Seconds_Behind_Master 逐渐缩小至 0。
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 172.16.1.125
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: binlog.000008
Read_Master_Log_Pos: 4013022
Relay_Log_File: hdp3-relay-bin.000002
Relay_Log_Pos: 638408
Relay_Master_Log_File: binlog.000008
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 638469
Relay_Log_Space: 4013168
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 52
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1125
Master_UUID: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: waiting for handler commit
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:5283-19980
Executed_Gtid_Set: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-7619
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)
(8)将 GTID 参数添加到主库的配置文件中
gtid_mode=on
enforce-gtid-consistency=true
至此完成联机配置 GTID 复制。
2. 联机更改复制模式
如果已经在未开启 GITD 的情况下配置了主从复制,可以联机将复制模式修改为 GTID 以及自动定位。由于整个过程不需要停止 MySQL 实例,这种方式适合在生产环境中使用。开始前确保 MySQL 服务器满足以下前提条件:
- 复制拓扑中的所有服务器都必须使用 MySQL 5.7.6 或更高版本。除非拓扑中的所有服务器都使用这些版本,否则无法在任何单个服务器上联机启用 GTID 事务。
- 所有服务器的 gtid_mode 默认设置为OFF。
以下过程可以随时暂停,之后再恢复,这使得该过程具有容错能力。如果过程中出现任何不相关的错误,可以先暂停过程解决问题,然后再从停止的地方继续。但至关重要的一点是,在继续下一步之前必须完成之前的步骤。联机改为 GTID 复制的步骤如下。
(1)在每台服务器上执行:
set global enforce_gtid_consistency=warn;
保证所有操作都与 GTID 兼容,并且确保错误日志中没有 GTID 的相关警告。
(2)在每台服务器上执行:
set global enforce_gtid_consistency=true;
(3)在每台服务器上执行:
set global gtid_mode=off_permissive;
哪个服务器首先执行此语句无关紧要,重要的是在开始下一步之前所有服务器完成步骤。
(4)在每台服务器上执行:
set global gtid_mode=on_permissive;
(5)在每台服务器上,等待状态变量 ongoing_anonymous_transaction_count 为 0。可以使用以下方法检查:
show status like 'ongoing_anonymous_transaction_count';
(6)如果二进制日志还用于复制以外的其它目的(如基于时间点的恢复等),需要在执行 flush logs 后备份二进制日志文件。包含 GTID 事务的二进制日志在下一步执行之后无法使用。完成此步后,确保拓扑中的任何位置都不存在 GTID 事务。
(7)在每台服务器上执行:
set global gtid_mode=on;
(8)在每个从库上执行以下操作:
stop slave;
change master to master_auto_position = 1;
start slave;
(9)在每台服务器上,将 gtid-mode = on 和 enforce_gtid_consistency=true 添加到 my.cnf。
现在可以保证所有事务都具有 GTID(步骤 5 或更早生成的事务已经过处理),已经改为 GTID 复制模式。
联机将 GTID 事务复制改为匿名事务复制模式的过程基本是上述步骤的逆过程,唯一不同的是等待记录事务复制的方式。
(1)在每个从库上执行以下操作:
stop slave;
show slave status\G
change master to master_auto_position = 0, master_log_file = 'xxx', master_log_pos = xxx;
start slave;
xxx 可以从 show slave status 输出的 Master_Log_File 和 Read_Master_Log_Pos 获得。
(2)在每台服务器上执行:
set global gtid_mode=on_permissive;
(3)在每台服务器上执行:
set global gtid_mode=off_permissive;
(4)在每台服务器上,等待变量 @@GLOBAL.GTID_OWNED 等于空字符串。可以使用以下方法检查:
select @@global.gtid_owned;
此时复制正常进行,但 slave status 中的 Retrieved_Gtid_Set和Executed_Gtid_Set 值不再变化。
(5)如果二进制日志还用于复制以外的其它目的(如基于时间点的恢复等),执行 FLUSH LOGS 后再备份二进制日志文件。包含 GTID 事务的二进制日志在下一步执行之后无法使用。完成此步后,确保拓扑中的任何位置都不存在 GTID 事务。
(6)在每台服务器上执行:
set global gtid_mode=off;
(7)在每台服务器上执行:
set global enforce_gtid_consistency=false;
(8)在每台服务器上,在 my.cnf 中设置 gtid-mode = off 和 enforce_gtid_consistency=false。
3. GTID 相关系统变量
前面已经在各个地方分散介绍过很多 GTID 相关的系统变量,如 gtid_executed、gtid_next、gtid_purged、gtid_mode 等,这里对 MySQL 8 中的重要 GTID 系统变量加以简单整理。
- binlog_gtid_simple_recovery:布尔类型全局变量,控制 MySQL 启动时从哪些 binlog 文件中寻找 GTID,缺省值为 ON。当 binlog_gtid_simple_recovery=true 时,初始化 gtid_executed 和 gtid_purged 值时只读取最老和最新的 binlog 文件。否则需要遍历所有 binlog 文件。
- enforce_gtid_consistency:枚举类型全局变量,指示是否强制 GTID 数据一致性,有效值为 OFF、ON、WARN,缺省值为 OFF。
- gtid_executed:全局和会话级别都可以用,用来保存已经执行过的 GTID 集合。
- gtid_executed_compression_period:整型全局变量,指示压缩 mysql.gtid_executed 表之前允许的事务数,缺省值为 1000。使用二进制日志时该值不起作用,而是在每个二进制日志轮转时压缩 mysql.gtid_executed 表。
- gtid_mode:枚举类型全局变量,控制是否开启 GTID 功能,有效值为 OFF、OFF_PERMISSIVE、ON_PERMISSIVE、ON,缺省值为 OFF。联机设置时,只能按顺序一步步修改。
- gtid_next:会话级枚举变量,用于指定是否以及如何获取下一个 GTID。有效值为 AUTOMATIC、ANONYMOUS、UUID:NUMBER,缺省值为 AUTOMATIC。
- gtid_owned:内部使用的只读变量,指示当前正在使用的 GTID 以及拥有它的线程 ID。
- gtid_purged:全局变量,设置已经执行但在 binlog 中被清除的 GTID 集合,是 gtid_executed 的子集。
二、GTID 运维
每个GTID唯一标识构成事务的一组二进制日志事件,在二进制日志中跟踪GTID事务与其事件集之间的映射。应用连接到数据库时,MySQL服务器自动跳过之前已处理的GTID事务,此行为对于自动复制定位和正确的故障转移至关重要。启用GTID也给运维带来了一些改变。
1. 跳过一个事务
传统基于二进制坐标的复制中,从库由于某些错误导致复制中断时,一个可能的解决方案是设置 sql_slave_skip_counter 全局系统变量,跳过导致错误的事件,然后重启复制。但启用 GTID 后,执行的单位由事件变为事务,因此该方法不再有效(slave_skip_errors 仍然可用),并会报以下错误。
mysql> set global sql_slave_skip_counter=1;
ERROR 1858 (HY000): sql_slave_skip_counter can not be set when the server is running with @@GLOBAL.GTID_MODE = ON. Instead, for each transaction that you want to skip, generate an empty transaction with the same GTID as the transaction
mysql>
从错误消息可以看到,GTID 跳过事务的方法是注入一个空事务,具体步骤为:
(1)定位出错事务的 GTID
从库报错我们需要获得从库执行的最后一个事务,方法有:
- show slave status \G 中的 Executed_Gtid_Set
- show global variables like '%gtid%'; 中的 gtid_executed
- show master status;中的Executed_Gtid_Set
(2)将会话级系统变量 gtid_next 设置为上一步的 GTID,如:
set gtid_next='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:980058'
注意 gtid_next 的值只能是单个 GTID。
(3)注入空事务
begin;commit;
(4)重启复制
set gtid_next='automatic';
start slave;
重启复制前需要将 gtid_next 设置为缺省值'automatic'。下面是个跳过多个事务的例子。
stop slave;
set gtid_next='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:980055';
begin;commit;
set gtid_next='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:980056';
begin;commit;
set gtid_next='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:980057';
begin;commit;
set gtid_next='automatic';
start slave;
2. mysqldump 导出
使用 mysqldump 受 set-gtid-purged 选项影响,set-gtid-purged 选项设置为 AUTO(默认值)或 ON 时的输出如下所示。
[mysql@hdp3~]$mysqldump --single-transaction --all-databases --master-data=2 --host=172.16.1.125 --user=repl --password=123456
-- MySQL dump 10.13 Distrib 8.0.16, for linux-glibc2.12 (x86_64)
--
-- Host: 172.16.1.125 Database:
-- ------------------------------------------------------
-- Server version 8.0.16
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
SET NAMES utf8mb4 ;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!50606 SET @OLD_INNODB_STATS_AUTO_RECALC=@@INNODB_STATS_AUTO_RECALC */;
/*!50606 SET GLOBAL INNODB_STATS_AUTO_RECALC=OFF */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
SET @MYSQLDUMP_TEMP_LOG_BIN = @@SESSION.SQL_LOG_BIN;
SET @@SESSION.SQL_LOG_BIN= 0;
--
-- GTID state at the beginning of the backup
--
SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-980059';
--
-- Position to start replication or point-in-time recovery from
--
-- CHANGE MASTER TO MASTER_LOG_FILE='binlog.000022', MASTER_LOG_POS=209837996;
SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN;
...
-- Dump completed on 2019-06-13 10:36:35
[mysql@hdp3~]$
开始部分的 SET @@SESSION.SQL_LOG_BIN= 0 防止导入数据时基于本地服务器生成新的 GTID。接着 GTID_PURGED 被设置为备份时刻已经执行过的 GTID 事务,该操作将会初始化 mysql.gtid_executed 表、gtid_purge 变量及 gtid_executed 变量。当 mysqldump 命令加入 --set-gtid-purged=off 选项时,则输出中不会加入 SQL_LOG_BIN= 0 和 GTID_PURGED 的设置。如果要将数据导入作为从库初始化,不能设置 --set-gtid-purged=off。下面是这个选项的含义。
--set-gtid-purged[=name]
Add 'SET @@GLOBAL.GTID_PURGED' to the output. Possible
values for this option are ON, OFF and AUTO. If ON is
used and GTIDs are not enabled on the server, an error is
generated. If OFF is used, this option does nothing. If
AUTO is used and GTIDs are enabled on the server, 'SET
@@GLOBAL.GTID_PURGED' is added to the output. If GTIDs
are disabled, AUTO does nothing. If no value is supplied
then the default (AUTO) value will be considered.
细心的用户到这里可能心生疑问:为初始化从库数据,命令行使用了 --all-databases 选项。mysql.gtid_executed 表会不会被重建,进而通过 GTID_PURGED 设置的 mysql.gtid_executed 表会重新改变,重启数据库后读取 mysql.gtid_executed 表可能获得错误 GTID 集合导致复制错误?答案也在 mysqldump 的输出中。
...
-- CHANGE MASTER TO MASTER_LOG_FILE='binlog.000022', MASTER_LOG_POS=209837996;
--
-- Current Database: `mysql`
--
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `mysql` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `mysql`;
...
首先,如果从库实例的 mysql 库存在则不会删除重建。
...
--
-- Table structure for table `gtid_executed`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
SET character_set_client = utf8mb4 ;
CREATE TABLE IF NOT EXISTS `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`)
) /*!50100 TABLESPACE `mysql` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `help_category`
--
...
其次,如果 mysql.gtid_executed 表存在则不会删除重建。最后,如果该表不存在则创建它,但不会向其装载数据。由此得出结论,除非手工删除了 mysql.gtid_executed 表,否则不会因它造成复制问题,至少 MySQL 8 是这样。
3. 主从切换
这里分三种情况进行讨论:从库只读、从库读写并且有全部写操作的二进制日志、从库读写但写操作的二进制日志不全。
(1)从库只读
这种情况从库(新主库)没有做过本地的事务,只需执行正常切换。
-- 从库(新主库)
stop slave;
reset slave all;
-- 主库(新从库)
change master to
master_host = '172.16.1.126',
master_port = 3306,
master_user = 'repl',
master_password = '123456',
master_auto_position = 1;
start slave;
新主库会生成自己的 GTID 事务,此时会出有两个 server_uuid 对应的 GTID:
mysql> select @@global.gtid_executed;
+-----------------------------------------------------------------------------------------+
| @@global.gtid_executed |
+-----------------------------------------------------------------------------------------+
| 53442434-8bfa-11e9-bc15-005056a50f77:1-2,
8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-980059 |
+-----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
(2)从库读写并且有全部写操作的二进制日志
-- 从库(新主库)
drop table test.t1;
create table test.t1(a int);
insert into test.t1 select 100;
stop slave;
reset slave all;
-- 主库(新从库)
change master to
master_host = '172.16.1.125',
master_port = 3306,
master_user = 'repl',
master_password = '123456',
master_auto_position = 1;
start slave;
此时在 show slave status 的输出中可以看到:
Retrieved_Gtid_Set: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:980060-980062
Executed_Gtid_Set: 53442434-8bfa-11e9-bc15-005056a50f77:1-2, 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-980062
刚才从库执行的三个本地事务,在新从库上正常复制。因为本地事务与复制事务 GTID 的 server_uuid 部分不同,只要 binlog 保留完整,从库上的写操作在主从切换后可以自动复制到新的从库上,与匿名复制相比明显方便许多。
(3)从库读写但写操作的二进制日志不全
-- 从库(新主库)
drop table test.t1;
create table test.t1(a int);
insert into test.t1 select 100;
stop slave;
reset slave all;
flush logs;
# 模拟binlog文件丢失
mv binlog.000022 binlog.000022.bak
-- 主库(新从库)
change master to
master_host = '172.16.1.125',
master_port = 3306,
master_user = 'repl',
master_password = '123456',
master_auto_position = 1;
start slave;
此时在 show slave status 的输出中报错如下:
Last_IO_Errno: 13114
Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Could not open log file'
真实环境要是遇到这种情况还是重建从库吧。二进制日志文件缺省的保留时间是 30 天(binlog_expire_logs_seconds = 2592000)。一般来说从库的写操作通常是为保留一些报表结果或临时数据,这些操作的最早时间很大可能已超过三十天,在这之后进行主从切换就会出现问题。这也是建议从库 readonly 的原因之一。如果确实要做比如加索引等不影响数据的操作可以在执行前设置 sql_log_bin 变量:
set sql_log_bin=0;
create index idx1 on test.t1(a);
这样不会增加本地 GTID。但还是要强调,从库最好始终 readonly。
三、GTID 限制
- 涉及非事务存储引擎的更新。使用 GTID 时,一条语句或一个事务中,不能对非事务性存储引擎(如 MyISAM)表和事务存储引擎(如 InnoDB)的表一起更新,因为这种混合引擎同时更新可能导致将多个 GTID 分配给同一事务。下面两组命令都会报同样的错误。
use test;
create table t_myisam(a int) engine=myisam;
create table t_innodb(a int) engine=innodb;
update t_myisam, t_innodb set t_myisam.a=1, t_innodb.a=1;
begin;
insert into t_myisam select 1;
insert into t_innodb select 1;
update t_myisam set a=2;
update t_innodb set a=2;
commit;
错误信息:
ERROR 1785 (HY000): Statement violates GTID consistency: Updates to non-transactional tables can only be done in either autocommitted statements or single-statement transactions, and never in the same statement as updates to transactional tables.
在 MySQL 8 中,这个限制并没有多大影响,因为包括系统表在内都是 InnoDB 表,缺省已经没有 myisam 表了,除非用户建表时显示定义。
- CREATE TABLE ... SELECT 语句。 CREATE TABLE ...使用基于 GTID 的复制时不允许使用 SELECT 语句。当 binlog_format 设置为 STATEMENT时,CREATE TABLE ... SELECT 语句作为一个具有单一 GTID 的事务记录在二进制日志中。但如果使用 ROW 格式,则该语句将记录为具有两个 GTID 的两个事务。如果主服务器使用 STATEMENT 格式而从服务器使用 ROW 格式,则从服务器将无法正确处理事务,因此 GTID 不允许使用 CREATE TABLE ... SELECT 语句来防止出现这种情况。
mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
1 row in set (0.00 sec)
mysql> create table t2 as select * from t1;
ERROR 1786 (HY000): Statement violates GTID consistency: CREATE TABLE ... SELECT.
mysql> set binlog_format=statement;
Query OK, 0 rows affected (0.00 sec)
mysql> create table t2 as select * from t1;
ERROR 1786 (HY000): Statement violates GTID consistency: CREATE TABLE ... SELECT.
mysql>
- 临时表。当 binlog_format 设置为 STATEMENT,服务器上启用 GTID 时,不能在事务、过程、函数或触发器内使用 CREATE TEMPORARY TABLE 和 DROP TEMPORARY TABLE 语句。如果设置了 autocommit = 1,则可以在使用 GTID 时在这些上下文之外使用它们。从 MySQL 8.0.13 开始,当 binlog_format 设置为 ROW 或 MIXED 时且启用 GTID 时,允许在事务、过程、函数或触发器内使用 CREATE TEMPORARY TABLE 和 DROP TEMPORARY TABLE 语句。这些语句不会写入二进制日志,因此不会复制到从库。
mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
1 row in set (0.01 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> create temporary table tmp1 select * from t1;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> set binlog_format=statement;
ERROR 3745 (HY000): Changing @@session.binlog_format is disallowed when the session has open temporary table(s). You could wait until these temporary table(s) are dropped and try again.
mysql> drop temporary table tmp1;
Query OK, 0 rows affected (0.00 sec)
mysql> set binlog_format=statement;
Query OK, 0 rows affected (0.00 sec)
mysql> create temporary table tmp1 select * from t1;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> drop temporary table tmp1;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> create temporary table tmp1 select * from t1;
ERROR 3748 (HY000): Statement violates GTID consistency: CREATE TEMPORARY TABLE and DROP TEMPORARY TABLE are not allowed inside a transaction or inside a procedure in a transactional context when @@session.binlog_format=STATEMENT.
mysql>
要防止执行会导致基于 GTID 的复制失败的语句,必须在启用 GTID 时使用 --enforce-gtid-consistency 选项启动所有服务器。这会导致前面讨论的语句失败并显示错误。
- 忽略服务器。使用 GTID 时,不推荐使用 CHANGE MASTER TO 语句的 IGNORE_SERVER_IDS 选项,因为已经应用的事务会自动被忽略。在启动基于 GTID 的复制之前,需要检查并清除(CHANGE MASTER TO IGNORE_SERVER_IDS = ();)之前在相关服务器上设置的所有忽略的服务器 ID 列表。可以为各个通道发出的 SHOW SLAVE STATUS 语句显示被忽略的服务器 ID 列表(如果有)。如果没有则 Replicate_Ignore_Server_Ids 字段为空。
- GTID 模式和 mysqldump。可以将使用 mysqldump 创建的转储导入到启用了 GTID 模式的 MySQL 服务器中,前提是目标服务器的二进制日志中没有重叠的 GTID。
[mysql@hdp3/usr/local/mysql/data]$mysqldump --single-transaction --all-databases --master-data=2 --host=172.16.1.125 --user=wxy --password=123456 | mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
mysqldump: [Warning] Using a password on the command line interface can be insecure.
Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of the database. If you don't want to restore GTIDs, pass --set-gtid-purged=OFF. To make a complete dump, pass --all-databases --triggers --routines --events.
ERROR 3546 (HY000) at line 26: @@GLOBAL.GTID_PURGED cannot be changed: the added gtid set must not overlap with @@GLOBAL.GTID_EXECUTED
mysqldump: Got errno 0 on write
[mysql@hdp3/usr/local/mysql/data]$mysql -uroot -p123456 -e "reset master;"
mysql: [Warning] Using a password on the command line interface can be insecure.
[mysql@hdp3/usr/local/mysql/data]$mysqldump --single-transaction --all-databases --master-data=2 --host=172.16.1.125 --user=wxy --password=123456 | mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
mysqldump: [Warning] Using a password on the command line interface can be insecure.
Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of the database. If you don't want to restore GTIDs, pass --set-gtid-purged=OFF. To make a complete dump, pass --all-databases --triggers --routines --events.
[mysql@hdp3/usr/local/mysql/data]$
四、GTID 集合运算函数
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。
mysql> select gtid_subset('','') c1,
-> gtid_subset('53442434-8bfa-11e9-bc15-005056a50f77:1','53442434-8bfa-11e9-bc15-005056a50f77:1') c2,
-> gtid_subset('53442434-8bfa-11e9-bc15-005056a50f77:1','53442434-8bfa-11e9-bc15-005056a50f77:1-10') c3,
-> gtid_subset('53442434-8bfa-11e9-bc15-005056a50f77:1','53442434-8bfa-11e9-bc15-005056a50f77:2-10') c4,
-> gtid_subset('53442434-8bfa-11e9-bc15-005056a50f77:1-2','53442434-8bfa-11e9-bc15-005056a50f77:1') c5,
-> gtid_subset('53442434-8bfa-11e9-bc15-005056a50f77:1-2','53442434-8bfa-11e9-bc15-005056a50f77:1-10') c6,
-> gtid_subset('53442434-8bfa-11e9-bc15-005056a50f77:1-2','53442434-8bfa-11e9-bc15-005056a50f77:2-10') c7,
-> gtid_subset('53442434-8bfa-11e9-bc15-005056a50f77:1-2','8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-2') c8,
-> gtid_subset('53442434-8bfa-11e9-bc15-005056a50f77:1-2','8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-2,53442434-8bfa-11e9-bc15-005056a50f77:1-2') c9;
+----+----+----+----+----+----+----+----+----+
| c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 |
+----+----+----+----+----+----+----+----+----+
| 1 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 1 |
+----+----+----+----+----+----+----+----+----+
1 row in set (0.00 sec)
- GTID_SUBTRACT(set1,set2):给定两个 GTID 集 set1 和 set2,仅返回 set1 与 set2 的差集。
mysql> select gtid_subtract('','') c1,
-> gtid_subtract('53442434-8bfa-11e9-bc15-005056a50f77:1','53442434-8bfa-11e9-bc15-005056a50f77:1') c2,
-> gtid_subtract('53442434-8bfa-11e9-bc15-005056a50f77:1','53442434-8bfa-11e9-bc15-005056a50f77:1-10') c3,
-> gtid_subtract('53442434-8bfa-11e9-bc15-005056a50f77:1','53442434-8bfa-11e9-bc15-005056a50f77:2-10') c4,
-> gtid_subtract('53442434-8bfa-11e9-bc15-005056a50f77:1-2','53442434-8bfa-11e9-bc15-005056a50f77:1') c5,
-> gtid_subtract('53442434-8bfa-11e9-bc15-005056a50f77:1-2','53442434-8bfa-11e9-bc15-005056a50f77:1-10') c6,
-> gtid_subtract('53442434-8bfa-11e9-bc15-005056a50f77:1-2','53442434-8bfa-11e9-bc15-005056a50f77:2-10') c7,
-> gtid_subtract('53442434-8bfa-11e9-bc15-005056a50f77:1-5','53442434-8bfa-11e9-bc15-005056a50f77:3-10') c8,
-> gtid_subtract('53442434-8bfa-11e9-bc15-005056a50f77:1-2,8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-2','8eed0f5b-6f9b-11e9-94a9-005056a57a4e:2-3') c9\G
*************************** 1. row ***************************
c1:
c2:
c3:
c4: 53442434-8bfa-11e9-bc15-005056a50f77:1
c5: 53442434-8bfa-11e9-bc15-005056a50f77:2
c6:
c7: 53442434-8bfa-11e9-bc15-005056a50f77:1
c8: 53442434-8bfa-11e9-bc15-005056a50f77:1-2
c9: 53442434-8bfa-11e9-bc15-005056a50f77:1-2,
8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1
1 row in set (0.00 sec)
- WAIT_FOR_EXECUTED_GTID_SET(gtid_set[, timeout]):等到服务器应用了包含在 gtid_set 中的所有事务。如果指定可选的 timeout 值(秒数),超时会使函数停止等待而退出。
mysql> select wait_for_executed_gtid_set('53442434-8bfa-11e9-bc15-005056a50f77:1');
+----------------------------------------------------------------------+
| wait_for_executed_gtid_set('53442434-8bfa-11e9-bc15-005056a50f77:1') |
+----------------------------------------------------------------------+
| 0 |
+----------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select wait_for_executed_gtid_set('53442434-8bfa-11e9-bc15-005056a50f77:1-7');
+------------------------------------------------------------------------+
| wait_for_executed_gtid_set('53442434-8bfa-11e9-bc15-005056a50f77:1-7') |
+------------------------------------------------------------------------+
| 0 |
+------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select wait_for_executed_gtid_set('53442434-8bfa-11e9-bc15-005056a50f77:8',5);
+------------------------------------------------------------------------+
| wait_for_executed_gtid_set('53442434-8bfa-11e9-bc15-005056a50f77:8',5) |
+------------------------------------------------------------------------+
| 1 |
+------------------------------------------------------------------------+
1 row in set (5.00 sec)
- WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS(gtid_set[, timeout][,channel]):与WAIT_FOR_EXECUTED_GTID_SET 类似,但针对单个启动的复制通道。
mysql> select wait_until_sql_thread_after_gtids('53442434-8bfa-11e9-bc15-005056a50f77:1');
+-----------------------------------------------------------------------------+
| wait_until_sql_thread_after_gtids('53442434-8bfa-11e9-bc15-005056a50f77:1') |
+-----------------------------------------------------------------------------+
| 0 |
+-----------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select wait_until_sql_thread_after_gtids('53442434-8bfa-11e9-bc15-005056a50f77:1-7');
+-------------------------------------------------------------------------------+
| wait_until_sql_thread_after_gtids('53442434-8bfa-11e9-bc15-005056a50f77:1-7') |
+-------------------------------------------------------------------------------+
| 0 |
+-------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select wait_until_sql_thread_after_gtids('53442434-8bfa-11e9-bc15-005056a50f77:8',5);
+-------------------------------------------------------------------------------+
| wait_until_sql_thread_after_gtids('53442434-8bfa-11e9-bc15-005056a50f77:8',5) |
+-------------------------------------------------------------------------------+
| -1 |
+-------------------------------------------------------------------------------+
1 row in set (5.00 sec)
2. 用户自定义函数
用户可以在自定义函数中调用这些内置函数,实现一些常用的 GTID 集合运算,下面是 MySQL 8 文档中的几个例子。
- 如果两个 GTID 集相同,函数返回非零值。
create function gtid_is_equal(gtid_set_1 longtext, gtid_set_2 longtext)
returns int deterministic
return gtid_subset(gtid_set_1, gtid_set_2) and gtid_subset(gtid_set_2, gtid_set_1);
- 如果两个 GTID 集不相交,函数返回非零值。
create function gtid_is_disjoint(gtid_set_1 longtext, gtid_set_2 longtext)
returns int deterministic
return gtid_subset(gtid_set_1, gtid_subtract(gtid_set_1, gtid_set_2));
- 如果两个 GTID 集不相交,则函数返回非零,sum 是两个集的并集。
create function gtid_is_disjoint_union(gtid_set_1 longtext, gtid_set_2 longtext, sum longtext)
returns int deterministic
return gtid_is_equal(gtid_subtract(sum, gtid_set_1), gtid_set_2) and
gtid_is_equal(gtid_subtract(sum, gtid_set_2), gtid_set_1);
- 函数返回格式化的 GTID 集。没有空格且没有重复,UUID 按字母顺序排列,间隔按数字顺序排列。
create function gtid_normalize(g longtext)
returns longtext deterministic
return gtid_subtract(g, '');
- 函数返回两个 GTID 集的并集。
create function gtid_union(gtid_set_1 longtext, gtid_set_2 longtext)
returns longtext deterministic
return gtid_normalize(concat(gtid_set_1, ',', gtid_set_2));
- 函数返回两个 GTID 集的交集。
create function gtid_intersection(gtid_set_1 longtext, gtid_set_2 longtext)
returns longtext deterministic
return gtid_subtract(gtid_set_1, gtid_subtract(gtid_set_1, gtid_set_2));
- 函数返回两个 GTID 集的对称差集。
create function gtid_symmetric_difference(gtid_set_1 longtext, gtid_set_2 longtext)
returns longtext deterministic
return gtid_subtract(concat(gtid_set_1, ',', gtid_set_2), gtid_intersection(gtid_set_1, gtid_set_2));
- 函数返回除去指定 UUID 的 GTID 集。
create function gtid_subtract_uuid(gtid_set longtext, uuid text)
returns longtext deterministic
return gtid_subtract(gtid_set, concat(uuid, ':1-', (1 << 63) - 2));
- 函数返回指定 UUID 的 GTID 集。
create function gtid_intersection_with_uuid(gtid_set longtext, uuid text)
returns longtext deterministic
return gtid_subtract(gtid_set, gtid_subtract_uuid(gtid_set, uuid));
3. 使用示例
(1)验证从库的复制是否最新
内置函数 GTID_SUBSET 和 GTID_SUBTRACT 可用于检查从库是应用了主库的每个事务。使用 GTID_SUBSET 执行此检查,在从库上执行以下命令:
master_gtid_executed=`mysql -uwxy -p123456 -h172.16.1.125 -N -e "select replace(@@global.gtid_executed,char(10),'')"`
slave_gtid_executed=`mysql -uwxy -p123456 -N -e "select replace(@@global.gtid_executed,char(10),'')"`
sql="select gtid_subset('$master_gtid_executed', '$slave_gtid_executed')"
mysql -uwxy -p123456 -e "$sql"
如果返回 0 则 master_gtid_executed 中的某些 GTID 不存在于 slave_gtid_executed 中,因此从库不是最新的。要使用 GTID_SUBTRACT 执行检查,请在从库上执行以下命令:
master_gtid_executed=`mysql -uwxy -p123456 -h172.16.1.125 -N -e "select replace(@@global.gtid_executed,char(10),'')"`
slave_gtid_executed=`mysql -uwxy -p123456 -N -e "select replace(@@global.gtid_executed,char(10),'')"`
sql="select gtid_subtract('$master_gtid_executed', '$slave_gtid_executed')"
mysql -uwxy -p123456 -e "$sql"
返回 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_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 时会出现错误。将输出中的 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 状态:
server1> SELECT GTID_IS_DISJOINT_UNION($original_gtid_executed,
$gtid_purged_set,
@@GLOBAL.gtid_executed);
如果结果为 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 表中。可以比较这些结果,以查找哪个从库的事务记录是最新的,即使并非所有事务都已提交。
在每个复制从属服务器上,通过发出以下语句来计算完整的事务记录:
SELECT GTID_UNION(RECEIVED_TRANSACTION_SET, @@GLOBAL.gtid_executed)
FROM performance_schema.replication_connection_status
WHERE channel_name = 'name';
然后比较每个从库的结果,选择具有最新事务记录的从库用作新主库。
(4)检查从库上的异常事务
自定义函数 GTID_SUBTRACT_UUID 可用于检查从库是否只接收到源自其指定主库的事务。对于单主复制,执行以下语句,server_uuid_of_master 是主库的 server_uuid:
SELECT GTID_SUBTRACT_UUID(@@GLOBAL.gtid_executed, server_uuid_of_master);
如果结果不为空,则返回的事务是不是源自指定主库的异常事务。对于多主复制拓扑中的从库,重复该功能,例如:
SELECT GTID_SUBTRACT_UUID(GTID_SUBTRACT_UUID(@@GLOBAL.gtid_executed,
server_uuid_of_master_1),
server_uuid_of_master_2);
如果结果不为空,则返回的事务是来自非指定主库的异常事务。
(5)验证复制拓扑中的服务器是否执行过本地事务
自定义函数 GTID_INTERSECTION_WITH_UUID 可用于验证服务器是否执行过本地事务。可以在服务器上发出以下语句来检查:
SELECT GTID_INTERSECTION_WITH_UUID(@@GLOBAL.gtid_executed, my_server_uuid);
(6)在多主复制设置中验证附加从库
假设 master1 和 master2 为双主复制,互为主从,同时 master2 还有自己的从库 slave3,如下图所示。
如果 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 集存储到客户端变量中,例如:
$master2_gtid_executed :=
master2> SELECT @@GLOBAL.gtid_executed;
$master2_server_uuid :=
master2> SELECT @@GLOBAL.server_uuid;
$slave_gtid_executed :=
slave3> SELECT @@GLOBAL.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 的所有事务。