Documentation Home

16.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. 在将二进制日志数据传输到副本并存储在副本的中继日志中后(使用已建立的机制进行此过程,请参阅 第 16.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.gtid_executed表是对副本应用的事务的完整记录。请注意,在 MySQL 5.7 中,将 GTID 插入表中的操作对于 DML 语句是原子的,但对于 DDL 语句则不是,因此如果服务器在涉及 DDL 语句的事务后意外退出,则 GTID 状态可能会变得不一致。从 MySQL 8.0 开始,该操作对于 DDL 语句和 DML 语句都是原子的。

  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 集可以被压缩。它还确保如果副本重新连接到源,则不会再次检索过滤掉的事务,如 第 16.1.3.3 节“GTID 自动定位”中所述。

在多线程副本(带有 slave_parallel_workers > 0)上,可以并行应用事务,因此复制的事务可以乱序提交(除非 slave_preserve_commit_order=1已设置)。当发生这种情况时, gtid_executed系统变量中的 GTID 集包含多个 GTID 范围,它们之间存在间隙。(在源或单线程副本上,GTID 单调递增,数字之间没有间隙。)多线程副本上的间隙仅出现在最近应用的事务中,并随着复制的进行而被填充。当使用STOP SLAVE语句,应用正在进行的事务以填补间隙。在服务器故障等关闭事件或使用 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 语句删除不同类型的表。

  • 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

You can change the value of gtid_purged in order to record on the server that the transactions in a certain GTID set have been applied, although they do not exist in any binary log on the server. When you add GTIDs to gtid_purged, they are also added to gtid_executed. An example use case for this action is when you are restoring a backup of one or more databases on a server, but you do not have the relevant binary logs containing the transactions on the server. In MySQL 5.7, you can only change the value of gtid_purged when gtid_executed (and therefore gtid_purged) is empty. For details of how to do this, see the description for gtid_purged.

The sets of GTIDs in the gtid_executed and gtid_purged system variables are initialized when the server starts. Every binary log file begins with the event Previous_gtids_log_event, which contains the set of GTIDs in all previous binary log files (composed from the GTIDs in the preceding file's Previous_gtids_log_event, and the GTIDs of every Gtid_log_event in the preceding file itself). The contents of Previous_gtids_log_event in the oldest and most recent binary log files are used to compute the gtid_executed and gtid_purged sets at server startup:

  • gtid_executed is computed as the union of the GTIDs in Previous_gtids_log_event in the most recent binary log file, the GTIDs of transactions in that binary log file, and the GTIDs stored in the mysql.gtid_executed table. This GTID set contains all the GTIDs that have been used (or added explicitly to gtid_purged) on the server, whether or not they are currently in a binary log file on the server. It does not include the GTIDs for transactions that are currently being processed on the server (@@GLOBAL.gtid_owned).

  • gtid_purged is computed by first adding the GTIDs in Previous_gtids_log_event in the most recent binary log file and the GTIDs of transactions in that binary log file. This step gives the set of GTIDs that are currently, or were once, recorded in a binary log on the server (gtids_in_binlog). Next, the GTIDs in Previous_gtids_log_event in the oldest binary log file are subtracted from gtids_in_binlog. This step gives the set of GTIDs that are currently recorded in a binary log on the server (gtids_in_binlog_not_purged). Finally, gtids_in_binlog_not_purged is subtracted from gtid_executed. The result is the set of GTIDs that have been used on the server, but are not currently recorded in a binary log file on the server, and this result is used to initialize gtid_purged.

If binary logs from MySQL 5.7.7 or older are involved in these computations, it is possible for incorrect GTID sets to be computed for gtid_executed and gtid_purged, and they remain incorrect even if the server is later restarted. For details, see the description for the binlog_gtid_simple_recovery system variable, which controls how the binary logs are iterated to compute the GTID sets. If one of the situations described there applies on a server, set binlog_gtid_simple_recovery=FALSE in the server's configuration file before starting it. That setting makes the server iterate all the binary log files (not just the newest and oldest) to find where GTID events start to appear. This process could take a long time if the server has a large number of binary log files without GTID events.

Resetting the GTID Execution History

If you need to reset the GTID execution history on a server, use the RESET MASTER statement. For example, you might need to do this after carrying out test queries to verify a replication setup on new GTID-enabled servers, or when you want to join a new server to a replication group but it contains some unwanted local transactions that are not accepted by Group Replication.

Warning

Use RESET MASTER with caution to avoid losing any wanted GTID execution history and binary log files.

Before issuing RESET MASTER, ensure that you have backups of the server's binary log files and binary log index file, if any, and obtain and save the GTID set held in the global value of the gtid_executed system variable (for example, by issuing a SELECT @@GLOBAL.gtid_executed statement and saving the results). If you are removing unwanted transactions from that GTID set, use mysqlbinlog to examine the contents of the transactions to ensure that they have no value, contain no data that must be saved or replicated, and did not result in data changes on the server.

When you issue RESET MASTER, the following reset operations are carried out:

  • The value of the gtid_purged system variable is set to an empty string ('').

  • The global value (but not the session value) of the gtid_executed system variable is set to an empty string.

  • The mysql.gtid_executed table is cleared (see mysql.gtid_executed Table).

  • If the server has binary logging enabled, the existing binary log files are deleted and the binary log index file is cleared.

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