目录
一、配置GTID复制
1. 联机配置GTID复制
2. 联机更改复制模式
3. GTID相关系统变量
二、GTID运维
1. 跳过一个事务
2. mysqldump导出
3. 主从切换
三、GTID限制
四、GTID集合运算函数
1. GTID内置函数
2. 用户自定义函数
3. 使用示例
上篇解释了许多GTID的原理,以及在MySQL复制中所起的作用,并且进行了很多实验加以辅助说明。本篇演示如何从头开始一步步配置GTID复制。实验环境同https://wxy0327.blog.csdn.net/article/details/90081518#%E4%BA%8C%E3%80%81%E5%A4%8D%E5%88%B6%E5%AE%9E%E9%AA%8C%E7%8E%AF%E5%A2%83。这里只讨论在联机情况下进行配置,因为相对于空库或脱机等理想情况,联机配置复制的需求更为典型和常见。
依照以前讨论异步复制时得出的结论,本实验使用XtraBackup工具进行联机数据备份。主、从服务器已经进行了以下配置:
这些作为配置MySQL复制的前置步骤,具体操作参考:https://wxy0327.blog.csdn.net/article/details/90081518#3.%20%E8%81%94%E6%9C%BA。现在说明联机配置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的可配置值为:
当enforce_gtid_consistency设置为true时,只能使用GTID安全的语句,例如如下操作不能与此选项一起使用:
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不同值的行为。每个条目的含义如下:
当二进制日志关闭且gtid_next设置为AUTOMATIC时,不会生成GTID,这与先前版本的行为一致。注意,为了保证主从数据一致性和实例恢复的性能,在MySQL 8中作为一项基本原则,除非有特殊需求,与复制相关的其它系统变量最好保持缺省值,包括但不限于下面所列出的系统变量:
(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复制。
如果已经在未开启GITD的情况下配置了主从复制,可以联机将复制模式修改为GTID以及自动定位。由于整个过程不需要停止MySQL实例,这种方式适合在生产环境中使用。开始前确保MySQL服务器满足以下前提条件:
以下过程可以随时暂停,之后再恢复,这使得该过程具有容错能力。如果过程中出现任何不相关的错误,可以先暂停过程解决问题,然后再从停止的地方继续。但至关重要的一点是,在继续下一步之前必须完成之前的步骤。联机改为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。
前面已经在各个地方分散介绍过很多GTID相关的系统变量,如gtid_executed、gtid_next、gtid_purged、gtid_mode等,这里对MySQL 8中的重要GTID系统变量加以简单整理。
每个GTID唯一标识构成事务的一组二进制日志事件,在二进制日志中跟踪GTID事务与其事件集之间的映射。应用连接到数据库时,MySQL服务器自动跳过之前已处理的GTID事务,此行为对于自动复制定位和正确的故障转移至关重要。启用GTID也给运维带来了一些改变。
传统基于二进制坐标的复制中,从库由于某些错误导致复制中断时,一个可能的解决方案是设置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;
使用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是这样。
这里分三种情况进行讨论:从库只读、从库读写并且有全部写操作的二进制日志、从库读写但写操作的二进制日志不全。(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。
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表了,除非用户建表时显示定义。
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>
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选项启动所有服务器。这会导致前面讨论的语句失败并显示错误。
[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]$
MySQL 8包含GTID_SUBSET、GTID_SUBTRACT、WAIT_FOR_EXECUTED_GTID_SET、WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS 4个内置函数,用于GTID集合的基本运算。
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)
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)
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)
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)
用户可以在自定义函数中调用这些内置函数,实现一些常用的GTID集合运算,下面是MySQL 8文档中的几个例子。
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);
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));
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);
create function gtid_normalize(g longtext)
returns longtext deterministic
return gtid_subtract(g, '');
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));
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));
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));
create function gtid_subtract_uuid(gtid_set longtext, uuid text)
returns longtext deterministic
return gtid_subtract(gtid_set, concat(uuid, ':1-', (1 << 63) - 2));
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));
(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集中以使该集完成。
server2> SELECT GTID_IS_EQUAL($gtid_purged_set, @@GLOBAL.gtid_executed);
如果结果为1,则两个GTID集相等,并且已正确计算该集。
server1> SELECT GTID_IS_DISJOINT($gtid_purged_set, @@GLOBAL.gtid_executed);
如果结果为1,则两个GTID集之间没有重叠,因此不存在重复的GTID。
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的所有事务。