Documentation Home

17.1.3.2 GTID 生命周期

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

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

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

  3. gtid_executed如果为事务分配了 GTID,则通过将 GTID 添加到系统变量 ( ) 中的 GTID 集合,GTID 以非原子方式外部化(事务提交后不久@@GLOBAL.gtid_executed)。此 GTID 集包含所有已提交 GTID 事务集的表示,并且在复制中用作表示服务器状态的令牌。启用二进制日志记录(根据源代码的要求), gtid_executed系统变量中的 GTID 集是应用事务的完整记录,但 mysql.gtid_executed表不是,因为最近的历史记录仍在当前二进制日志文件中。

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

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

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

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

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

  9. gtid_executed在副本上提交复制的事务后不久,通过将 GTID 添加到副本的系统变量 ( @@GLOBAL.gtid_executed) 中 的 GTID 集,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 集可以被压缩。它还确保如果副本重新连接到源,则不会再次检索过滤掉的事务,如 第 17.1.3.3 节“GTID 自动定位”中所述。

在多线程副本(使用 replica_parallel_workers > 0or slave_parallel_workers > 0)上,事务可以并行应用,因此复制的事务可以乱序提交(除非 设置了replica_preserve_commit_order=1 or slave_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 COMMITorXA ROLLBACK阶段分配了单独的 GTID。XA 事务是持久准备的,以便用户可以提交它们或在发生故障时回滚它们(在复制拓扑中可能包括到另一台服务器的故障转移)。因此,事务的两个部分被单独复制,所以它们必须有自己的 GTID,即使回滚的非 XA 事务不会有 GTID。

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

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

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

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

系统gtid_next变量

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

  • gtid_next设置为 时 AUTOMATIC,这是默认值,并且事务已提交并写入二进制日志,服务器会自动生成并分配一个新的 GTID。如果一个事务由于其他原因被回滚或没有写入二进制日志,服务器不会生成和分配一个 GTID。

  • 如果您设置gtid_next为有效的 GTID(由 UUID 和事务序列号组成,用冒号分隔),服务器会将该 GTID 分配给您的事务。gtid_executed即使事务未写入二进制日志或事务为空时,也会 分配和添加此 GTID 。

请注意,在设置 gtid_next为特定 GTID 并且事务已提交或回滚后,SET @@SESSION.gtid_next必须在任何其他语句之前发出显式语句。AUTOMATIC如果您不想明确分配任何更多 GTID,则可以使用它来将 GTID 值设置回。

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

客户端可以通过@@SESSION.gtid_next在执行事务之前设置为特定的 GTID 来模拟复制的事务。mysqlbinlog使用此技术 生成二进制日志的转储,客户端可以重播该转储以保留 GTID。通过client提交的模拟复制事务与通过replication applier线程提交的复制事务完全等同,事后无法区分。

系统gtid_purged变量

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

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

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

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

您可以更改 的值, gtid_purged以便在服务器上记录已应用某个 GTID 集中的事务,尽管它们不存在于服务器上的任何二进制日志中。当您将 GTID 添加到 时 gtid_purged,它们也会添加到gtid_executed. 此操作的一个示例用例是当您在服务器上恢复一个或多个数据库的备份时,但您没有包含服务器上事务的相关二进制日志。在 MySQL 8.0 之前,您只能更改 gtid_purgedwhen 的值gtid_executed(因此 gtid_purged) 是空的。从 MySQL 8.0 开始,这个限制不再适用,您还可以选择是否将整个 GTID 集替换 gtid_purged为指定的 GTID 集,或者将指定的 GTID 集添加到已经存在的 GTID 中 gtid_purged。有关如何执行此操作的详细信息,请参阅 的说明 gtid_purged

gtid_executed和 系统变量中 的 GTID 集在 gtid_purged服务器启动时初始化。每个二进制日志文件都以 event 开头 Previous_gtids_log_event,它包含所有先前二进制日志文件中的 GTID 集(由前面文件中的 GTID 和前面文件本身Previous_gtids_log_event中每个文件的 GTID 组成)。最旧和最新的二进制日志文件中的 Gtid_log_event内容 用于在服务器启动 时计算和 集合:Previous_gtids_log_eventgtid_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 相加来计算的。此步骤给出了当前或曾经记录在服务器 ( gtids_in_binlog) 二进制日志中的一组 GTID。接下来,从Previous_gtids_log_event最旧的二进制日志文件中减去 GTID gtids_in_binlog。此步骤给出了当前记录在服务器 ( gtids_in_binlog_not_purged) 二进制日志中的一组 GTID。最后,gtids_in_binlog_not_purged减去 gtid_executed. 结果是在服务器上使用过的一组 GTID,但当前没有记录在服务器上的二进制日志文件中,这个结果用于初始化 gtid_purged.

如果这些计算涉及来自 MySQL 5.7.7 或更早版本的二进制日志,则可能会为gtid_executed和 计算出不正确的 GTID 集gtid_purged,并且即使稍后重新启动服务器,它们仍然不正确。有关详细信息,请参阅 binlog_gtid_simple_recovery 系统变量的说明,该变量控制二进制日志如何迭代以计算 GTID 集。如果那里描述的情况之一适用于服务器,请设置 binlog_gtid_simple_recovery=FALSE 在启动服务器之前在服务器的配置文件中。该设置使服务器迭代所有二进制日志文件(不仅仅是最新的和最旧的)以查找 GTID 事件开始出现的位置。如果服务器有大量没有 GTID 事件的二进制日志文件,此过程可能需要很长时间。

重置 GTID 执行历史

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

警告

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

在发出之前RESET MASTER,确保您有服务器的二进制日志文件和二进制日志索引文件的备份,如果有的话,并获取并保存在 gtid_executed系统变量的全局值中保存的 GTID 集(例如,通过发出SELECT @@GLOBAL.gtid_executed语句并保存结果)。如果要从该 GTID 集中删除不需要的事务,请使用mysqlbinlog检查事务的内容以确保它们没有价值,不包含必须保存或复制的数据,并且不会导致服务器上的数据更改。

当您发出RESET MASTER时,将执行以下重置操作:

  • 系统变量的值 gtid_purged设置为空字符串 ( '')。

  • 系统变量的全局值(但不是会话值) gtid_executed设置为空字符串。

  • mysql.gtid_executed表已清除(请参阅 mysql.gtid_executed 表)。

  • 如果服务器启用了二进制日志记录,则删除现有的二进制日志文件并清除二进制日志索引文件。

请注意,这RESET MASTER是重置 GTID 执行历史记录的方法,即使服务器是禁用二进制日志记录的副本。 RESET REPLICA对 GTID 执行历史没有影响。