记录备忘
参考博文:https://blog.youkuaiyun.com/wzy0623/article/details/91047395
目录
9.1.2 CREATE TABLE ... SELECT语句
一、概述
GTID出现之前,在一主多从的复制拓扑中,如果主库宕机,需要从多个从库选择之一作为新主库,这个过程比较复杂。没有一种直接了当的方法找到其它从库对应的新主库二进制日志坐标。通常的做法是先要寻找每个从库复制原主库的最后语句,然后找到新主库中包含该语句的二进制日志文件,其中该语句后的第一个事件位置即为连接新主库的二进制坐标。主要难点在于不存在一个唯一标识指出“复制原主库的最后语句”,于是后来的MySQL中就出现了GTID的概念。
全局事务标识符GTID(全称为Global Transaction Identifier),是在整个复制环境中对一个事务的唯一标识。它是MySQL 5.6加入的一个强大特性,目的在于能够实现主从自动定位和切换,而不像以前需要指定文件和位置。使用GTID复制时,主库上提交事务时创建事务对应的GTID,从库在应用中继日志时用GTID识别和跟踪每个事务。在启动新从库或因故障转移到新主库时可以使用GTID来标识复制的位置,极大地简化了这些任务。由于GTID的复制完全基于事务,因此只要在主库上提交的所有事务也在从库上提交,两者之间的一致性就得到保证。GTID支持基于语句或基于行的复制格式,但为了获得最佳效果,MySQL建议使用基于行的格式。GTID始终保留在主库和从库上,这意味着可以通过检查其二进制日志来确定应用于任何从库的任何事务的来源。而且,一旦在给定库上提交了具有给定GTID的事务,则该库将忽略具有相同GTID的任何后续事务。因此,在主库上提交的事务只会在从库上应用一次,这也有助于保证一致性。
二、格式与存储
2.1 单个GTID
GTID与主库上提交的每个事务相关联。此标识符不仅对发起事务的库是唯一的,而且在给定复制拓扑中的所有库中都是唯一的。GTID用冒号分隔的一对坐标表示,例如:
8eed0f5b-6f9b-11e9-94a9-005056a57a4e:23
前一部分是主库的server_uuid,后面一部分是主库上按提交事务的顺序确定的序列号,提交的事务序号从1开始。上面显式的GTID表示:具有8eed0f5b-6f9b-11e9-94a9-005056a57a4e的服务器上提交的第23个事务具有此GTID。server_uuid保存在data下的auto.cnf中,MySQL启动的时候会读取auto.cnf文件,如果没有读取到则会生成一个server_uuid,并保存到auto.cnf文件中。
2.2 GTID集
2.2.1 源自同一服务器的一系列GTID
GTID集是包括一个或多个单个GTID或GTID范围的集合。源自同一服务器的一系列GTID可以折叠为单个表达式,例如:
8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-321
上面的示例表示源自server_uuid为8eed0f5b-6f9b-11e9-94a9-005056a57a4e服务器的第1到第321个事务。源自同一服务器的多个单GTID或GTID范围可以同时包含在由冒号分隔的单个表达式中,例如:
8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-3:11:47-49
2.2.2 源自不同服务器一系列GTID
GTID集可以包括单个GTID和GTID范围的任意组合,甚至它可以包括源自不同服务器的GTID。例如一个存储在从库gtid_executed系统变量中的GTID集可能如下:
565a6b0a-6f05-11e9-b95c-005056a5497f:1-20, 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-321
表示该从库已从两个主库应用了事务(也有可能是在从库执行的写操作)。当从库变量返回GTID集时,UUID按字母顺序排列,并且数值间隔按升序合并。
MySQL服务器中很多地方都用到GTID集,例如:gtid_executed和gtid_purged系统变量存储的值是GTID集;START SLAVE的UNTIL SQL_BEFORE_GTIDS和UNTIL SQL_AFTER_GTIDS子句的值是GTID集;内置函数GTID_SUBSET()和GTID_SUBTRACT()需要GTID集作为输入等。
2.3 存储
mysql.gtid_executed表记录的是服务器上已经执行事务的GTID。三个字段分别表示发起事务的服务器UUID、UUID集的起始和结束事务ID。对于单个GTID,后两个字段的值相同。
mysql> select * from mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| bf8e15c0-ee60-11ea-a1a1-0800271813aa | 1 | 1 |
| bf8e15c0-ee60-11ea-a1a1-0800271813aa | 2 | 2 |
| bf8e15c0-ee60-11ea-a1a1-0800271813aa | 3 | 3 |
| bf8e15c0-ee60-11ea-a1a1-0800271813aa | 4 | 4 |
+--------------------------------------+----------------+--------------+
4 rows in set (0.00 sec)
仅当gtid_mode设置为ON或ON_PERMISSIVE时,GTID才存储在mysql.gtid_executed表中。存储的GTID值取决于是是否开启二进制日志:
- 对于从库,如果禁用了二进制日志记录(skip-log-bin)或log_slave_updates,则服务器将在该表中存储每个事务的GTID。
- 如果启用了二进制日志记录,当刷新二进制日志或重启服务器时,服务器都会将当前二进制日志中所有事务的GTID写入mysql.gtid_executed表。这种情况适用于主库或启用了二进制日志记录的从库。
启用二进制日志记录时,mysql.gtid_executed表并不保存所有已执行事务的GTID的完整记录,该信息由gtid_executed全局系统变量的值提供。如果服务器意外停止,则当前二进制日志文件中的GTID集不会保存在mysql.gtid_executed表中。在MySQL实例恢复期间,这些GTID将从二进制日志文件添加到表中。即使服务器处于只读模式,MySQL服务器也可以写入mysql.gtid_executed表,这样二进制日志文件仍然可以在只读模式下轮转。如果无法访问mysql.gtid_executed表时进行二进制日志文件轮转,则继续使用二进制日志文件存储GTID,同时在服务器上记录警告信息
2019-06-03T09:37:07.777423Z 287633 [Warning] [MY-010015] [Repl] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
前面已经提到,mysql.gtid_executed表的记录可能并不是完整的已执行GTID,而且有不可访问的可能性(例如误删除此表),因此建议始终通过查询@@global.gtid_executed(每次提交后更新)来确认MySQL服务器的GTID状态,而不是查询mysql.gtid_executed表。mysql.gtid_executed表可能随着事务量的增多而快速膨胀,存储了源自同一服务器的大量不同的单个GTID,这些GTID构成一个范围,例如:
mysql> select * from mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| bf8e15c0-ee60-11ea-a1a1-0800271813aa | 1 | 4 |
+--------------------------------------+----------------+--------------+
4 rows in set (0.00 sec)
为了节省空间,MySQL服务器定期压缩mysql.gtid_executed表,方法是将上边每个这样的行集替换为跨越整个事务标识符间隔的单行。
通过设置gtid_executed_compression_period系统变量,可以控制压缩表之前允许的事务数,从而控制压缩率。此变量的默认值为1000,指的是在每1000次事务之后执行表的压缩。将gtid_executed_compression_period设置为0将不执行压缩。注意,启用二进制日志时不使用gtid_executed_compression_period的值,并在每个二进制日志轮转时压缩mysql.gtid_executed表。mysql.gtid_executed表的压缩由名为thread/sql/compress_gtid_table的专用前台线程执行。此线程未在SHOW PROCESSLIST的输出中列出,但可以从performance_schema.threads中查询到:
mysql> select * from performance_schema.threads where name like '%gtid%'\G
*************************** 1. row ***************************
THREAD_ID: 44
NAME: thread/sql/compress_gtid_table
TYPE: FOREGROUND
PROCESSLIST_ID: 6
PROCESSLIST_USER: NULL
PROCESSLIST_HOST: NULL
PROCESSLIST_DB: NULL
PROCESSLIST_COMMAND: Daemon
PROCESSLIST_TIME: 438302
PROCESSLIST_STATE: Suspending
PROCESSLIST_INFO: NULL
PARENT_THREAD_ID: 1
ROLE: NULL
INSTRUMENTED: YES
HISTORY: YES
CONNECTION_TYPE: NULL
THREAD_OS_ID: 73199
RESOURCE_GROUP: SYS_default
1 row in set (0.00 sec)
mysql>
通常该线程都处于暂停状态,只有当满足条件时被唤醒,如达到gtid_executed_compression_period或发生了二进制日志轮转(如flush logs等)时。
三、GTID自动跳过
在主库上提交客户端事务时,如果事务已写入二进制日志,则会为其分配新的GTID,保证为客户事务生成单调递增且没有间隙的GTID。如果未将客户端事务写入二进制日志(例如,因为事务已被过滤掉,或者事务是只读的),则不会在源服务器上为其分配GTID。从库上复制的事务保留与主库上事务相同的GTID。即使从库上未开启二进制日志,GTID也会被保存。MySQL系统表mysql.gtid_executed用于保存MySQL服务器上应用的所有事务的GTID,但存储在当前活动二进制日志文件中的事务除外。
GTID的自动跳过功能意味着一旦在给定服务器上提交了具有给定GTID的事务,则该服务器将忽略使用相同GTID执行的任何后续事务(这种情况是可能发生的,如手工设置了gtid_next时)。这有助于保证主从一致性,因为在主库上提交的事务在从库上应用不超过一次。如果具有给定GTID的事务已开始在服务器上执行但尚未提交或回滚,则任何在该服务器上启动具有相同GTID的并发事务都将被阻止。服务器既不执行并发事务也不将控制权返回给客户端。一旦先前的事务提交或回滚,就可以继续执行同一GTID上被阻塞的并发会话。如果是回滚,则一个并发会话继续执行事务,并且在同一GTID上阻塞的任何其它并发会话仍然被阻止。如果是提交,则所有并发会话都将被阻止,并自动跳过事务的所有语句。mysqlbinlog的输出中的GTID_NEXT包含事务的GTID,用于标识复制中的单个事务。
下面做三个简单实验验证GTID的自动跳过功能
3.1 相同GTID自动跳过
3.1.1 准备数据
mysql> use db_test;
mysql> create table t1(a int);
Query OK, 1 rows affected (0.02 sec)
mysql> create table t2(a int);
Query OK, 1 rows affected (0.02 sec)
mysql> insert into t1 values(1),(2);
Query OK, 2 rows affected (0.02 sec)
mysql> insert into t2 values(1),(2);
Query OK, 2 rows affected (0.02 sec)
3.1.2 查看当前GTID
mysql> show master status \G
*************************** 1. row ***************************
File: bin-log.000025
Position: 508
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: bf8e15c0-ee60-11ea-a1a1-0800271813aa:1-2
1 row in set (0.00 sec)
3.1.3 将GDIT设置为已经执行过的值,再执行事务。
-- 设置为已经执行过的编号2
mysql> set gtid_next = 'bf8e15c0-ee60-11ea-a1a1-0800271813aa:2';
Query OK, 0 rows affected (0.00 sec)
mysql> truncate table t1;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
| 2 |
+------+
2 rows in set (0.00 sec)
mysql> set gtid_next = automatic;
Query OK, 0 rows affected (0.00 sec)
可以看到,服务器已经执行了GTID为2的事务,后续相同GTID的事务都被自动跳过,虽然truncate语句没有报错,但并未执行,数据无变化。
3.2 相同GTID事务,事务1提交,事务2被跳过
准备两个SQL脚本s1.sql、s2.sql,gtid_next是一个没用过的新值s1.sql内容如下:
set gtid_next='bf8e15c0-ee60-11ea-a1a1-0800271813aa:3';
begin;
delete from db_test.t1 where a=1;
select sleep(10);
commit;
set gtid_next=automatic;
s2.sql内容如下:
set gtid_next='bf8e15c0-ee60-11ea-a1a1-0800271813aa:3';
begin;
delete from db_test.t2 where a=1;
select sleep(10);
commit;
set gtid_next=automatic;
在会话1执行s1.sql,并且在其sleep期间,在会话2执行s2.sql
查看结果
mysql> select * from t1;
+------+
| a |
+------+
| 2 |
+------+
1 row in set (0.00 sec)
mysql> select * from t2;
+------+
| a |
+------+
| 1 |
| 2 |
+------+
2 rows in set (0.00 sec)
可以看到,事务1回滚前,事务2被阻塞。事务1回滚后,具有相同GTID的事务2被提交。回滚没有占用GTID。
3.3 相同GTID事务,事务1回滚,事务2提交
准备两个SQL脚本s1.sql、s2.sql,gtid_next是一个没用过的新值s1.sql内容如下:
set gtid_next='bf8e15c0-ee60-11ea-a1a1-0800271813aa:4';
begin;
delete from db_test.t1 where a=2;
select sleep(10);
rollback;
set gtid_next=automatic;
s2.sql内容如下:
set gtid_next='bf8e15c0-ee60-11ea-a1a1-0800271813aa:4';
begin;
delete from db_test.t2 where a=1;
select sleep(10);
commit;
set gtid_next=automatic;
在会话1执行s1.sql,并且在其sleep期间,在会话2执行s2.sql
查看结果
mysql> select * from t1;
+------+
| a |
+------+
| 2 |
+------+
1 row in set (0.00 sec)
mysql> select * from t2;
+------+
| a |
+------+
| 2 |
+------+
2 rows in set (0.00 sec)
可以看到,事务1提交前,事务2被阻塞。事务1提交后,具有相同GTID的事务2被跳过。
四、GTID生命周期
典型事务的GTID的生命周期包括以下步骤:
- 客户端事务在主库上执行并提交,此事务被分配一个GTID,该GTID由主服务器的UUID和此服务器上尚未使用的最小非零事务序列号组成。GTID作为Gtid_log_event紧接在事务本身之前,与事务本身一起被写入主库的二进制日志,这是一个原子操作。如果未将客户端事务写入二进制日志(例如,因为事务已被过滤掉,或者事务是只读的),则不会为其分配GTID。轮转二进制日志或关闭MySQL实例时,都会将写入之前二进制日志文件的所有事务的GTID写入mysql.gtid_executed表。
- 如果为事务分配了GTID,则将GTID添加到主库gtid_executed系统变量(@@global.gtid_executed)的GTID集合中,这步将在事务提交后进行,并且与事务处理本身不是一个原子操作。gtid_executed系统变量包含所有已提交事务的GTID集,是应用事务的完整记录,并在复制中用作表示服务器状态的标记。mysql.gtid_executed表不包含当前二进制日志文件中的最新GTID记录。
- 在将二进制日志数据传输到从库并存储在从库的中继日志中之后,从库读取GTID并将其设置为gtid_next系统变量的值。这告诉从库必须使用此GTID记录下一个事务。
- 在处理事务本身之前,从库首先读取和检查复制事务的GTID,不仅保证没有先前事务具有此GTID,而且还保证没有其它会话已经读取此GTID但尚未提交相关事务。因此,如果多个客户端同时提交同一GTID事务,则服务器只允许其中一个执行。从库的gtid_owned系统变量(@@global.gtid_owned)显示当前正在使用的GTID以及拥有它的线程ID。如果已经使用了该GTID,通过自动跳过功能忽略具该事务,并且不会引发错误。
- 如果GTID尚未使用,则从库应用复制的事务。gtid_next设置为主库已分配的GTID,从库不会为此事务生成新的GTID,而是使用存储在gtid_next中的GTID。
- 如果在从库上启用了二进制日志记录,则与主库操作类似。GTID会在提交时作为Gtid_log_event原子写入其二进制日志。当轮转二进制日志或关闭MySQL实例时,都会将写入之前二进制日志文件的所有事务的GTID写入mysql.gtid_executed表。
- 如果从库禁用二进制日志记录,则通过将GTID直接写入mysql.gtid_executed表保留GTID。MySQL会在事务中附加一条语句,将GTID插入该表中。从MySQL 8.0开始,此操作对于DDL语句和DML语句都是原子操作。在这种情况下,mysql.gtid_executed表是从库上应用事务的完整记录。
- 从库提交复制事务后,GTID将被添加到从库gtid_executed系统变量(@@global.gtid_executed)的GTID集合中,这步将在事务应用后进行,并且与事务处理本身不是一个原子操作。
主库上过滤掉的客户端事务未分配GTID,因此它们不会添加到gtid_executed系统变量中的事务集中,也不会添加到mysql.gtid_executed表中。但是,在从库上过滤掉的复制事务的GTID是持久化的。如果在从库上启用了二进制日志,则过滤掉的事务将作为Gtid_log_event写入其二进制日志,后跟仅包含BEGIN和COMMIT语句的空事务。如果禁用二进制日志,则已过滤掉的事务的GTID将写入mysql.gtid_executed表。为过滤掉的事务保留GTID可确保可以将mysti.gtid_executed表和gtid_executed系统变量中的GTID用GTID集表示。它还确保如果从库重新连接到主库,不会再次检索过滤掉的事务。
五、GTID的分配
- 典型情况是服务器为已提交的事务生成新的GTID。
- 写入二进制日志的每个数据库更改(DDL或DML)都会分配一个GTID。这包括自动提交的更改以及使用BEGIN和COMMIT或START TRANSACTION语句提交的更改。
- 当数据库,以及非表数据库对象,例如过程、函数、触发器、事件、视图、用户、角色在创建、更改或删除时会分配GTID。
- 授权语句和非事务表的更新也会分配GTID。
- 当二进制日志中的生成语句自动删除表时,会为该语句分配GTID。例如,当具有打开临时表的用户会话断开连接时,将自动删除临时表,或者使用MEMORY存储引擎的表在服务器启动后第一次访问时会自动删除。
- 调用存储过程时,为过程提交的每个更新事务生成一个GTID。
- 多表DROP TABLE语句中包含任何不支持原子DDL存储引擎的表(如myisam)或临时表,会生成多个GTID。
注意,触发器内的语句和触发它的语句是在一个事务中,因此不会单独分配GTID。MySQL不支持类似Oracle自治事务的功能。未写入二进制日志事务不会分配GTID。这包括回滚的事务,或在禁用二进制日志时执行的事务,或指定 sql_log_bin=0 时执行的事务,或空事务(begin;commit;)等。
六、GTID变量
6.1 gtid_next系统变量
gtid_next是会话系统变量。默认情况下,对于在用户会话中提交的新事务,服务器会自动生成并分配新的GTID。在从库上应用事务时,将保留来自原始服务器的GTID。可以通过设置gtid_next系统变量的会话值来更改此行为:
- 当gtid_next设置为AUTOMATIC(默认值),并且事务已提交并写入二进制日志时,服务器会自动生成并分配新的GTID。如果由于其它原因而回滚事务或未将事务写入二进制日志,则服务器不会生成和分配GTID。
- 如果将gtid_next设置为有效的单个GTID(由UUID和事务序列号组成,用冒号分隔),服务器会将该GTID分配给下一个事务。只要事务提交,就会将此GTID分配并添加到gtid_executed。
在将gtid_next设置为特定GTID并且已提交或回滚事务之后,必须在任何其它语句之前发出显式SET @@SESSION.gtid_next语句。如果不想分配更多GTID,可以将此选项值的值设置回AUTOMATIC。
前面已经提到(见“3.1 相同GTID自动跳过”),从库的SQL线程应用复制事务时使用此技术,将@@SESSION.gtid_next显式设置为在源服务器上分配给事务的GTID。这意味着保留来自原始服务器的GTID,而不是由从库生成和分配的新GTID。即使从库禁用log_bin或log_slave_updates,或者事务是空操作或在从库上过滤掉时,GTID也会添加到从库上的gtid_executed。
客户端可以通过在执行事务之前将@@SESSION.gtid_next设置为特定GTID来模拟复制的事务。mysqlbinlog使用此技术生成二进制日志的转储,客户端可以重放该转储以保留GTID。通过客户端提交的模拟复制事务完全等同于通过复制应用程序线程提交的复制事务,并且事后无法区分它们。
6.2 gtid_purged系统变量
gtid_purged是全局系统变量。@@GLOBAL.gtid_purged中的GTID集包含已在服务器上提交,但在服务器上的任何二进制日志文件中不存在的所有事务的GTID。gtid_purged是gtid_executed的子集。以下类别的GTID位于gtid_purged中:
- 在从库上禁用二进制日志记录时提交的复制事务的GTID。
- 已清除的二进制日志文件中事务的GTID。
- 通过语句SET @@GLOBAL.gtid_purged明确添加到集合中的GTID。
七、搭建GTID复制
7.1 规划
- 虚拟服务器
IP | 角色 |
---|---|
192.168.56.11 | 主 |
192.168.56.12 | 从 |
192.168.56.13 | 从 |
- 软件
软件名称 | 软件版本 |
---|---|
OS | CentOS Linux release 7.3.1611 (Core) |
MySQL | mysql-8.0.21 |
7.2 配置复制
7.2.1 修改配置文件
# 其他参数略
server-id = 11
gtid_mode = on
log-bin = /home/mysql/mysql8/logs/binlog/bin-log
log-bin-index = /home/mysql/mysql8/logs/binlog/bin-log.index
relay-log = /home/mysql/mysql8/logs/relaylog/relay-log
relay-log-index = /home/mysql/mysql8/logs/relaylog/relay-log.index
binlog_format = row
enforce_gtid_consistency = on
7.2.2 启动复制
通过xtrabackup备份主库,然后恢复到俩个从库。详细步骤见“MySQL复制(一)—— 异步复制”中的5.3.2.2 xtrabackup方式 。master_auto_position = 1;自动寻找GTID位置。
change master to
master_host = '192.168.56.11',
master_port = 3308,
master_user = 'repl',
master_password = '123456',
master_auto_position = 1;
八、GTID运维
8.1 跳过一个事物
传统基于二进制坐标的复制中,从库由于某些错误导致复制中断时,一个可能的解决方案是设置sql_slave_skip_counter全局系统变量,跳过导致错误的事件,然后重启复制。但启用GTID后,执行的单位由事件变为事务,因此该方法不再有效。
正确的步骤如下;
-- 停止从库
stop slave;
-- 查看从库最后的事务
show slave status \G; -- 中的 Executed_Gtid_Set。
show global variables like '%gtid%'; -- 中的 gtid_executed 。
show master status; -- 中的Executed_Gtid_Set。
-- 设置会话级的gtid_next为刚才查到的GTID
set gtid_next='bf8e15c0-ee60-11ea-a1a1-0800271813aa:1-6';
-- 开始一个空事物
begin; commit;
-- 设置GTID为自动,启动从库
set gtid_next='automatic';
start slave;
8.2 主从切换
这里分三种情况进行讨论:从库只读、从库读写并且有全部写操作的二进制日志、从库读写但写操作的二进制日志不全。
8.2.1 从库只读
这种情况从库(新主库)没有做过本地的事务,只需执行正常切换。
-- 从库(将要作为新主库)
stop slave;
reset slave all;
-- 主库(指向原来的从库)
change master to
master_host = '192.168.56.13',
master_port = 3308,
master_user = 'repl',
master_password = '123456',
master_auto_position = 1;
start slave;
新主库会生成自己的GTID事务,此时会出有两个server_uuid对应的GTID:
mysql> select @@global.gtid_executed;
+-----------------------------------------------------------------------------------------+
| @@global.gtid_executed |
+-----------------------------------------------------------------------------------------+
| 727bd38d-f0d2-11ea-9aa0-0800271813aa:1,bf8e15c0-ee60-11ea-a1a1-0800271813aa:1-6 |
+-----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
8.2.2 从库读写并且有全部写操作的二进制日志
-- 从库(将要切换为主库),执行事务生成新的GTID
create table lw_test.t2(a int);
insert into lw_test.t2 select 100;
stop slave;
reset slave all;
-- 主库(将要切换为从库)
change master to
master_host = '192.168.56.12',
master_port = 3308,
master_user = 'repl',
master_password = '123456',
master_auto_position = 1;
start slave;
刚才从库执行的本地事务,在新从库上正常复制。因为本地事务与复制事务GTID的server_uuid部分不同,只要binlog保留完整,从库上的写操作在主从切换后可以自动复制到新的从库上(会有t2表)。
mysql> select @@global.gtid_executed;
+-----------------------------------------------------------------------------------------+
| @@global.gtid_executed |
+-----------------------------------------------------------------------------------------+
| 636959f3-f0d2-11ea-b67d-0800271813aa:1,727bd38d-f0d2-11ea-9aa0-0800271813aa:1 |
+-----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
8.2.3 从库读写但写操作的二进制日志不全
这种情况下主从切换会失败,只能重建主从。
九、GTID限制
9.1 内置函数
9.1.1 涉及非事务存储引擎的更新
使用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.
9.1.2 CREATE TABLE ... 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>
9.1.3 临时表
当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>