文档首页
MySQL 8.4 参考手册
相关文档 下载本手册
PDF (US Ltr) - 39.9Mb
PDF (A4) - 40.0Mb
手册页 (TGZ) - 258.5Kb
手册页 (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


19.1.3.2 GTID 生命周期

GTID 的生命周期包括以下步骤

  1. 在源上执行并提交事务。此客户端事务将分配一个 GTID,该 GTID 由源的 UUID 和此服务器上尚未使用的最小非零事务序列号组成。GTID 将写入源的二进制日志(在日志中紧接在事务本身之前)。如果未将客户端事务写入二进制日志(例如,因为事务被过滤掉,或事务是只读的),则不会为其分配 GTID。

  2. 如果为事务分配了 GTID,则 GTID 会在提交时通过将其写入二进制日志的事务开头(作为 Gtid_log_event)来原子地持久化。每当二进制日志轮换或服务器关闭时,服务器会将写入到先前二进制日志文件中的所有事务的 GTID 写入 mysql.gtid_executed 表中。

  3. 如果为事务分配了 GTID,则 GTID 会在事务提交后(在事务提交后很短时间内)通过将其添加到 gtid_executed 系统变量 (@@GLOBAL.gtid_executed) 的 GTID 集中非原子地外部化。此 GTID 集包含已提交 GTID 事务集的表示形式,它在复制中用作表示服务器状态的令牌。如果启用了二进制日志记录(如源所需),则 gtid_executed 系统变量中的 GTID 集是已应用事务的完整记录,但 mysql.gtid_executed 表不是,因为最新历史记录仍然在当前二进制日志文件中。

  4. 在将二进制日志数据传输到副本并存储在副本的中继日志中后(使用为此过程建立的机制,有关详细信息,请参见 第 19.2 节,“复制实现”),副本会读取 GTID 并将 gtid_next 系统变量的值设置为此 GTID。这告诉副本下一个事务必须使用此 GTID 进行日志记录。请注意,副本在会话上下文中设置 gtid_next

  5. 副本会验证在 gtid_next 中,还没有线程拥有 GTID 以处理事务。通过首先读取和检查复制的事务的 GTID(在处理事务本身之前),副本不仅保证在副本上尚未应用具有此 GTID 的任何先前事务,而且还保证没有其他会话已经读取此 GTID 但尚未提交相关事务。因此,如果多个客户端尝试同时应用相同的事务,服务器会通过只允许其中一个执行来解决此问题。副本的 gtid_owned 系统变量 (@@GLOBAL.gtid_owned) 显示当前正在使用的每个 GTID 以及拥有它的线程的 ID。如果 GTID 已经使用过,则不会引发错误,并且会使用自动跳过功能来忽略事务。

  6. 如果 GTID 未被使用,副本会应用复制的事务。因为 gtid_next 被设置为源已经分配的 GTID,副本不会尝试为该事务生成新的 GTID,而是使用存储在 gtid_next 中的 GTID。

  7. 如果副本启用了二进制日志记录,则 GTID 会在提交时以原子方式持久化,方法是在事务开始时将其写入二进制日志(作为 Gtid_log_event)。每当二进制日志轮转或服务器关闭时,服务器都会将写入之前二进制日志文件的所有事务的 GTID 写入 mysql.gtid_executed 表。

  8. 如果副本禁用了二进制日志记录,则 GTID 会通过直接写入 mysql.gtid_executed 表以原子方式持久化。MySQL 将一个语句附加到事务中,以将 GTID 插入到表中。对于 DDL 语句和 DML 语句,此操作都是原子的。在这种情况下,mysql.gtid_executed 表是副本上应用的事务的完整记录。

  9. 在复制事务在副本上提交后不久,GTID 会以非原子方式外部化,方法是将其添加到副本的 gtid_executed 系统变量 (@@GLOBAL.gtid_executed) 中的 GTID 集合中。与源一样,此 GTID 集合包含已提交的 GTID 事务集合的表示。如果副本禁用了二进制日志记录,则 mysql.gtid_executed 表也是副本上应用的事务的完整记录。如果副本启用了二进制日志记录,这意味着某些 GTID 仅记录在二进制日志中,则 gtid_executed 系统变量中的 GTID 集合是唯一的完整记录。

在源上完全过滤掉的客户端事务不会被分配 GTID,因此不会添加到 gtid_executed 系统变量中的事务集合中,也不会添加到 mysql.gtid_executed 表中。但是,复制的事务的 GTID 会在副本上完全过滤掉并持久化。如果副本启用了二进制日志记录,则过滤掉的事务会作为 Gtid_log_event 写入二进制日志,后面跟着一个仅包含 BEGINCOMMIT 语句的空事务。如果禁用了二进制日志记录,则过滤掉的事务的 GTID 会写入 mysql.gtid_executed 表。保留过滤掉的事务的 GTID 可确保 mysql.gtid_executed 表和 gtid_executed 系统变量中的 GTID 集合可以被压缩。它还确保在副本重新连接到源时不会再次检索过滤掉的事务,如 第 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 也可以分配给除事务之外的其他更改,在某些情况下,单个事务可以分配多个 GTID。

每个写入二进制日志的数据库更改(DDL 或 DML)都会分配 GTID。这包括自动提交的更改以及使用 BEGINCOMMITSTART TRANSACTION 语句提交的更改。GTID 还会分配给数据库的创建、更改或删除,以及非表数据库对象的创建、更改或删除,例如过程、函数、触发器、事件、视图、用户、角色或授权。

非事务性更新和事务性更新都会分配 GTID。此外,对于非事务性更新,如果在尝试写入二进制日志缓存时发生磁盘写入故障,从而导致二进制日志中出现间隙,则生成的事件日志事件将分配 GTID。

当二进制日志中的生成语句自动删除表时,会为该语句分配 GTID。当副本开始应用刚刚启动的源的事件时,临时表会自动删除,当使用基于语句的复制时 (binlog_format=STATEMENT),并且具有打开的临时表的用户会话断开连接时,也是如此。使用 MEMORY 存储引擎的表在服务器启动后第一次访问时会自动删除,因为在关闭期间可能丢失了行。

当事务未写入源服务器的二进制日志时,服务器不会为其分配 GTID。这包括回滚的事务和在源服务器上禁用二进制日志记录时执行的事务,无论是全局禁用(在服务器的配置中指定 --skip-log-bin)还是针对会话禁用 (SET @@SESSION.sql_log_bin = 0)。这也包括使用基于行的复制时 (binlog_format=ROW) 的无操作事务。

XA 事务会为事务的 XA PREPARE 阶段和事务的 XA COMMITXA ROLLBACK 阶段分配单独的 GTID。XA 事务会持久准备,以便用户可以在发生故障时提交或回滚事务(在复制拓扑中,这可能包括故障转移到另一个服务器)。因此,事务的两个部分会分别复制,因此它们必须有自己的 GTID,即使回滚的非 XA 事务不会有 GTID。

在以下特殊情况下,单个语句可以生成多个事务,因此可以分配多个 GTID

  • 调用提交多个事务的存储过程。为过程提交的每个事务生成一个 GTID。

  • 多表 DROP TABLE 语句会删除不同类型的表。如果任何表使用不支持原子 DDL 的存储引擎,或者如果任何表是临时表,则可能会生成多个 GTID。

  • 在使用基于行的复制时 (binlog_format=ROW),发出 CREATE TABLE ... SELECT 语句。会为 CREATE TABLE 操作生成一个 GTID,并为行插入操作生成一个 GTID。

gtid_next 系统变量

默认情况下,对于用户会话中提交的新事务,服务器会自动生成并分配新的 GTID。当事务在副本上应用时,来自源服务器的 GTID 会被保留。您可以通过设置 gtid_next 系统变量的会话值来更改此行为。

  • gtid_next 设置为 AUTOMATIC(默认值)并且事务提交并写入二进制日志时,服务器会自动生成并分配新的 GTID。如果事务回滚或由于其他原因未写入二进制日志,则服务器不会生成并分配 GTID。

  • 如果您将 gtid_next 设置为 AUTOMATIC:TAG,则会为每个新事务分配一个包含指定标签的新 GTID。

  • 如果您将 gtid_next 设置为有效的 GTID(包含 UUID、可选标签和事务序列号,用冒号分隔),则服务器会将该 GTID 分配给您的事务。即使事务未写入二进制日志或事务为空,也会分配此 GTID 并将其添加到 gtid_executed 中。

请注意,在将 gtid_next 设置为特定 GTID(以 UUID:NUMBERUUID:TAG:NUMBER 格式)后,并且事务已提交或回滚,在执行任何其他语句之前必须发出显式 SET @@SESSION.gtid_next 语句。如果不想再显式分配任何 GTID,可以使用此方法将 GTID 值设置回 AUTOMATIC

当复制应用线程应用复制的事务时,它们会使用此技术,将 @@SESSION.gtid_next 显式设置为源服务器上分配的复制事务的 GTID。这意味着来自源服务器的 GTID 会被保留,而不是由副本生成并分配新的 GTID。这也意味着 GTID 会添加到副本的 gtid_executed 中,即使副本禁用了二进制日志记录或副本更新日志记录,或者事务是无操作事务或在副本上被过滤掉。

客户端可以通过在执行事务之前将 @@SESSION.gtid_next 设置为特定 GTID 来模拟复制的事务。这种技术被 mysqlbinlog 用于生成二进制日志的转储,客户端可以重播转储以保留 GTID。通过客户端提交的模拟复制事务与通过复制应用线程提交的复制事务完全等效,并且在事后无法区分它们。

gtid_purged 系统变量

gtid_purged 系统变量 (@@GLOBAL.gtid_purged) 中的 GTID 集合包含在服务器上已提交的所有事务的 GTID,但这些事务不存在于服务器上的任何二进制日志文件中。 gtid_purgedgtid_executed 的子集。以下类别中的 GTID 属于 gtid_purged

  • 在副本上禁用二进制日志记录时提交的复制事务的 GTID。

  • 写入已清除的二进制日志文件的那些事务的 GTID。

  • 通过语句 SET @@GLOBAL.gtid_purged 显式添加到集合中的 GTID。

您可以更改 gtid_purged 的值,以便在服务器上记录某个 GTID 集中的事务已应用,即使这些事务在服务器上的任何二进制日志中都不存在。当您将 GTID 添加到 gtid_purged 时,它们也会被添加到 gtid_executed。此操作的一个用例是,当您在服务器上还原一个或多个数据库的备份时,但您没有服务器上包含这些事务的相关二进制日志。您还可以选择用指定的 GTID 集替换 gtid_purged 中的整个 GTID 集,或者将指定的 GTID 集添加到 gtid_purged 中已有的 GTID 中。有关如何执行此操作的详细信息,请参阅 gtid_purged 的描述。

在服务器启动时,会初始化 gtid_executedgtid_purged 系统变量中的 GTID 集。每个二进制日志文件都以 Previous_gtids_log_event 事件开头,其中包含所有先前二进制日志文件中的 GTID 集(由前一个文件中的 Previous_gtids_log_event 的 GTID 以及前一个文件本身中的每个 Gtid_log_event 的 GTID 组成)。最旧和最新的二进制日志文件中的 Previous_gtids_log_event 的内容用于在服务器启动时计算 gtid_executedgtid_purged 集。

  • gtid_executed 被计算为最新二进制日志文件中 Previous_gtids_log_event 中的 GTID、该二进制日志文件中事务的 GTID 以及 mysql.gtid_executed 表中存储的 GTID 的并集。此 GTID 集包含服务器上使用过(或显式添加到 gtid_purged)的所有 GTID,无论它们当前是否在服务器上的二进制日志文件中。它不包括当前正在服务器上处理的事务的 GTID (@@GLOBAL.gtid_owned)。

  • gtid_purged 的计算方法是首先添加最新二进制日志文件中 Previous_gtids_log_event 中的 GTID 以及该二进制日志文件中事务的 GTID。此步骤将提供当前或曾经记录在服务器上的二进制日志文件中的 GTID 集 (gtids_in_binlog)。接下来,将最旧二进制日志文件中 Previous_gtids_log_event 中的 GTID 从 gtids_in_binlog 中减去。此步骤将提供当前记录在服务器上的二进制日志文件中的 GTID 集 (gtids_in_binlog_not_purged)。最后,将 gtids_in_binlog_not_purgedgtid_executed 中减去。结果是服务器上使用过但当前未记录在服务器上的二进制日志文件中的 GTID 集,此结果用于初始化 gtid_purged

重置 GTID 执行历史记录

如果您需要重置服务器上的 GTID 执行历史记录,请使用 RESET BINARY LOGS AND GTIDS 语句。您可能需要在执行测试查询以验证新的 GTID 启用服务器上的复制设置后执行此操作,或者当您想要将新的服务器加入到复制组中,但它包含一些不受 Group Replication 接受的本地事务时执行此操作。

警告

谨慎使用 RESET BINARY LOGS AND GTIDS 以避免丢失任何想要的 GTID 执行历史记录和二进制日志文件。

在发出 RESET BINARY LOGS AND GTIDS 之前,请确保您已备份服务器的二进制日志文件和二进制日志索引文件(如果有),并获取并保存 gtid_executed 系统变量的全局值中保存的 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 执行历史记录没有影响。