文档首页
MySQL 9.0 参考手册
相关文档 下载本手册
PDF (美国标准信封) - 40.0Mb
PDF (A4) - 40.1Mb
手册页 (TGZ) - 258.2Kb
手册页 (Zip) - 365.3Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 9.0 参考手册  /  ...  /  GTID 生命周期

19.1.3.2 GTID 生命周期

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

  1. 在一个事务在源上执行并提交。这个客户端事务被分配一个 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 值设置回 AUTOMATIC,如果您不想再显式分配任何 GTID。

当复制应用线程应用复制的事务时,它们会使用此技术,将 @@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)。接下来,从 gtids_in_binlog 中减去最旧二进制日志文件中的 Previous_gtids_log_event 中的 GTID。此步骤将提供当前在服务器上的二进制日志中记录的 GTID 集 (gtids_in_binlog_not_purged)。最后,从 gtid_executed 中减去 gtids_in_binlog_not_purged。结果是已在服务器上使用过但当前未在服务器上的二进制日志文件中记录的 GTID 集,此结果用于初始化 gtid_purged

重置 GTID 执行历史记录

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

警告

谨慎使用 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 执行历史记录没有影响。