GTID 的生命周期由以下步骤组成:
-
在源服务器上执行和提交事务。这笔客户事务被分配了一个 GTID,组成部分是源服务器的 UUID 和该服务器上尚未使用的最小非零事务序列号。GTID 被写入源服务器的二进制日志中(在日志中的事务之前)。如果客户事务未写入二进制日志(例如,因为事务被过滤掉了,或者事务是只读的),那么它就不会被分配 GTID。
-
如果事务被分配了 GTID,那么在提交时 GTID 将被原子地写入二进制日志中(作为
Gtid_log_event
)。每当二进制日志被旋转或服务器关闭时,服务器将写入前一个二进制日志文件中的所有事务的 GTID 到mysql.gtid_executed
表中。 -
如果事务被分配了 GTID,那么它将被外部化(在事务提交后很短的时间内),通过将其添加到
gtid_executed
系统变量(@@GLOBAL.gtid_executed
)中的 GTID 集合中。该 GTID 集合包含了所有已提交事务的表示,并且在复制中用作服务器状态的标记。启用二进制日志记录(如源服务器所需),gtid_executed
系统变量中的 GTID 集合是所有已应用事务的完整记录,而mysql.gtid_executed
表则不是,因为最新的历史记录仍然在当前的二进制日志文件中。 -
在二进制日志数据被传输到副本并存储在副本的中继日志中(使用已建立的机制来完成此过程,详见 第 19.2 节,“复制实现”),副本读取 GTID 并将其
gtid_next
系统变量设置为该 GTID。这告诉副本,下一个事务必须使用该 GTID 记录。需要注意的是,副本在会话上下文中设置gtid_next
。 -
副本验证没有线程已经拥有了
gtid_next
中的 GTID,以便处理事务。通过首先读取和检查复制事务的 GTID,然后再处理事务本身,副本保证不仅没有以前的事务已经应用于副本,也没有其他会话已经读取了该 GTID 但尚未提交相关事务。因此,如果多个客户端尝试并发应用同一事务,服务器将通过让其中一个执行来解决该问题。gtid_owned
系统变量(@@GLOBAL.gtid_owned
)显示副本当前正在使用的每个 GTID 和拥有它的线程 ID。如果 GTID 已经被使用,則不會引发错误,并使用自动跳过函数来忽略事务。 -
如果 GTID 尚未被使用,副本将应用复制事务。因为
gtid_next
已经被设置为源服务器分配的 GTID,副本不会尝试生成新的 GTID 来记录该事务,而是使用gtid_next
中存储的 GTID。 -
如果在副本上启用了二进制日志记录,GTID 将被原子地写入二进制日志中(作为
Gtid_log_event
)。每当二进制日志被旋转或服务器关闭时,服务器将写入前一个二进制日志文件中的所有事务的 GTID 到mysql.gtid_executed
表中。 -
如果在副本上禁用了二进制日志记录,GTID 将被原子地写入
mysql.gtid_executed
表中。MySQL 将在事务中追加一条语句,以将 GTID 插入表中。该操作对于 DDL 语句和 DML 语句都是原子的。在这种情况下,mysql.gtid_executed
表是副本上应用事务的完整记录。 -
非常短暂地,在副本上提交复制事务后,GTID 将非原子地 externalized 到副本的
gtid_executed
系统变量(@@GLOBAL.gtid_executed
)中。对于源服务器,这个 GTID 集包含了所有提交的 GTID 事务的表示形式。如果在副本上禁用了二进制日志记录,那么mysql.gtid_executed
表也是副本上应用的事务的完整记录。如果在副本上启用了二进制日志记录,那么gtid_executed
系统变量中的 GTID 集是唯一的完整记录。
在源服务器上完全过滤掉的客户端事务不会被分配 GTID,因此它们不会被添加到 gtid_executed
系统变量中,也不会被添加到 mysql.gtid_executed
表中。然而,在副本上完全过滤掉的复制事务的 GTID 将被持久化。如果在副本上启用了二进制日志记录,那么过滤掉的事务将被写入二进制日志作为一个 Gtid_log_event
,后跟一个仅包含 BEGIN
和 COMMIT
语句的空事务。如果在副本上禁用了二进制日志记录,那么过滤掉的事务的 GTID 将被写入 mysql.gtid_executed
表中。保留过滤掉的事务的 GTID 可以确保 mysql.gtid_executed
表和 gtid_executed
系统变量可以被压缩。它还确保了如果副本重新连接到源服务器时,不会再次检索过滤掉的事务,如 第 19.1.3.3 节“GTID 自动定位”所述。
在多线程副本(replica_parallel_workers > 0
)上,事务可以并行应用,因此复制事务可以以非顺序方式提交(除非 replica_preserve_commit_order = 1
)。当这种情况发生时,gtid_executed
系统变量中的 GTID 集将包含多个 GTID 范围,之间存在间隙。(在源服务器或单线程副本上,GTID 是单调递增的,没有间隙。)多线程副本上的间隙只出现在最近应用的事务中,并随着复制的进度被填充。当使用 STOP REPLICA
语句停止复制线程时,正在进行的事务将被应用以填充间隙。在服务器故障或使用 KILL
语句停止复制线程的情况下,间隙可能会保留。
典型的情况是服务器为提交的事务生成新的 GTID。然而,GTID 也可以被分配给其他类型的更改,而在某些情况下,单个事务可以被分配多个 GTID。
每个数据库更改(DDL 或 DML),无论是 autocommitted 的还是使用 BEGIN
和 COMMIT
或 START TRANSACTION
语句提交的,都将被分配 GTID。这包括创建、修改或删除数据库,以及创建、修改或删除非表数据库对象,如过程、函数、触发器、事件、视图、用户、角色或授权。
非事务更新和事务更新都将被分配 GTID。此外,对于非事务更新,如果在写入二进制日志缓存时出现磁盘写入失败,从而在二进制日志中创建了间隙,那么结果事件日志事件也将被分配 GTID。
当表自动被生成的语句在二进制日志中删除时,会分配一个GTID给该语句。临时表在复制开始应用源事件时自动删除,或者在基于语句的复制中(binlog_format=STATEMENT
)用户会话断开连接时。使用MEMORY
存储引擎的表在服务器启动后第一次访问时自动删除,因为可能在关闭期间丢失了行。
当事务不写入服务器的二进制日志时,不会分配GTID。这包括回滚的事务和在服务器上禁用二进制日志记录时执行的事务,或者在会话中禁用二进制日志记录(SET @@SESSION.sql_log_bin = 0
)。这也包括基于行的复制中(binlog_format=ROW
)noop事务。
XA事务将分配单独的GTID用于事务的XA COMMIT
或
XA ROLLBACK
阶段。XA事务是持久准备的,以便用户可以在失败时提交或回滚(在复制拓扑中可能包括故障转移到另一个服务器)。因此,这两个事务部分将被单独复制,因此它们必须拥有自己的GTID,即使非XA事务回滚不需要GTID。
在以下特殊情况下,单个语句可以生成多个事务,因此被分配多个GTID:
-
存储过程被调用,提交多个事务。每个事务都生成一个GTID。
-
多表
DROP TABLE
语句删除不同类型的表。如果任何表使用不支持原子DDL的存储引擎,或者任何表是临时表,可能生成多个GTID。 -
在基于行的复制中(
binlog_format=ROW
),CREATE TABLE ... SELECT
语句被执行。一个GTID生成用于CREATE TABLE
操作,另一个GTID生成用于行插入操作。
默认情况下,对于用户会话中的新事务,服务器自动生成和分配新的GTID。当事务在副本上应用时,原始服务器的GTID将被保留。你可以通过设置会话值gtid_next
系统变量来更改此行为:
-
当
gtid_next
设置为AUTOMATIC
(默认值)时,并且事务被提交并写入二进制日志,服务器自动生成和分配新的GTID。如果事务回滚或未写入二进制日志,服务器不生成和分配GTID。 -
如果你设置
gtid_next
为AUTOMATIC:
,每个新事务将被分配一个新的GTID,包括指定的标签。TAG
-
如果你设置
gtid_next
为有效的GTID(由UUID、可选标签和事务序列号组成,使用冒号分隔),服务器将该GTID分配给你的事务。即使事务不写入二进制日志,或者事务为空,该GTID也将被分配和添加到gtid_executed
中。
注意,在设置gtid_next
为特定的GTID(在
或UUID
:NUMBER
格式)后,并且事务已经提交或回滚,必须在发出任何其他语句之前发出明确的UUID
:TAG
:NUMBER
SET @@SESSION.gtid_next
语句。你可以使用这来将GTID值设置回AUTOMATIC
,如果你不想再明确分配任何GTID。
当复制应用程序线程应用复制事务时,它们使用这种技术,明确地将@@SESSION.gtid_next
设置为来自原始服务器的GTID。这意味着来自原始服务器的GTID被保留,而不是由副本生成和分配新的GTID。这也意味着GTID被添加到gtid_executed
中,即使在副本上禁用了二进制日志记录或副本更新日志记录,或者事务是no-op或在副本上被过滤掉。
客户端可以通过在执行事务之前将@@SESSION.gtid_next
设置为特定的GTID来模拟复制事务。这项技术由mysqlbinlog用于生成二进制日志的转储,以便客户端可以重放以保留GTID。通过客户端模拟的复制事务提交与通过复制应用程序线程提交的复制事务完全等效,无法在事后区分。
系统变量gtid_purged
(@@GLOBAL.gtid_purged
)包含了在服务器上提交的所有事务的GTID,但这些事务不在服务器上的任何二进制日志文件中。gtid_purged
是gtid_executed
的子集。以下类别的GTID在gtid_purged
中:
-
在副本上禁用二进制日志记录时提交的复制事务的GTID。
-
已经被清除的二进制日志文件中的事务的GTID。
-
通过语句
SET @@GLOBAL.gtid_purged
明确添加到集合中的GTID。
你可以更改gtid_purged
的值,以便在服务器上记录某个GTID集合中的事务已经应用,尽管它们不在服务器上的任何二进制日志中。当你将GTID添加到gtid_purged
时,它们也将被添加到gtid_executed
中。一个使用这种操作的示例是,当你恢复服务器上的一个或多个数据库的备份,但你没有包含这些事务的二进制日志时。你也可以选择是否将gtid_purged
中的整个GTID集合替换为指定的GTID集合,或者将指定的GTID集合添加到gtid_purged
中。有关如何执行此操作的详细信息,请参阅gtid_purged
的描述。
GTID 集在 gtid_executed
和 gtid_purged
系统变量中初始化时,服务器启动时。每个二进制日志文件以事件 Previous_gtids_log_event
开始,该事件包含所有前一个二进制日志文件中的 GTID 集(来自前一个文件的 Previous_gtids_log_event
,以及该文件本身的 GTID 事件)。最旧和最新的二进制日志文件中的 Previous_gtids_log_event
内容用于计算服务器启动时的 gtid_executed
和 gtid_purged
集:
-
gtid_executed
是通过将最新二进制日志文件中的Previous_gtids_log_event
、该文件中的事务 GTID 和mysql.gtid_executed
表中的 GTID 组合计算的。该 GTID 集包含服务器上使用的所有 GTID(无论是否当前在二进制日志文件中),包括添加到gtid_purged
中的 GTID。它不包括当前服务器上处理的事务的 GTID(@@GLOBAL.gtid_owned
)。 -
gtid_purged
是通过首先将最新二进制日志文件中的Previous_gtids_log_event
和该文件中的事务 GTID 相加计算的。然后,从gtids_in_binlog
中减去最旧二进制日志文件中的Previous_gtids_log_event
。最后,从gtid_executed
中减去gtids_in_binlog_not_purged
。结果是服务器上使用的 GTID 集,但当前不在二进制日志文件中,该结果用于初始化gtid_purged
。
如果涉及到 MySQL 5.7.7 或更早版本的二进制日志,那么可能会计算出错误的 GTID 集用于 gtid_executed
和 gtid_purged
,即使服务器后来重新启动。有关详细信息,请参阅 binlog_gtid_simple_recovery
系统变量的描述,该变量控制如何迭代二进制日志来计算 GTID 集。如果服务器上出现这种情况,请在服务器配置文件中设置 binlog_gtid_simple_recovery=FALSE
,然后重新启动服务器。这将使服务器迭代所有二进制日志文件(而不仅仅是最新和最旧的)以找到 GTID 事件的开始位置。这可能需要很长时间,如果服务器有许多不包含 GTID 事件的二进制日志文件。
如果需要在服务器上重置 GTID 执行历史记录,请使用 RESET BINARY LOGS AND GTIDS
语句。你可能需要在新的 GTID 启用的服务器上执行测试查询以验证复制设置,或者当你想将新服务器加入复制组但它包含一些不需要的本地事务时。
请小心使用 RESET BINARY LOGS AND GTIDS
,以免丢失想要的 GTID 执行历史记录和二进制日志文件。
在发出 RESET BINARY LOGS AND GTIDS
之前,请确保您备份了服务器的二进制日志文件和二进制日志索引文件(如果有),并获取并保存全局值中的GTID集合(例如,通过发出 SELECT @@GLOBAL.gtid_executed
语句并保存结果)。如果您从该GTID集合中删除不需要的事务,请使用 mysqlbinlog 查看事务的内容,以确保它们没有价值,不包含需要保存或复制的数据,并且没有在服务器上导致数据更改。
当您发出 RESET BINARY LOGS AND GTIDS
时,以下重置操作将被执行:
-
系统变量
gtid_purged
的值将被设置为空字符串(''
)。 -
系统变量
gtid_executed
的全局值(但不是会话值)将被设置为空字符串。 -
表
mysql.gtid_executed
将被清除(见 mysql.gtid_executed 表)。 -
如果服务器启用了二进制日志记录,则现有的二进制日志文件将被删除,二进制日志索引文件将被清除。
请注意,RESET BINARY LOGS AND GTIDS
是重置GTID执行历史的方法,即使服务器是禁用二进制日志记录的副本。RESET REPLICA
对GTID执行历史没有影响。