Documentation Home

17.1.3.1 GTID 格式和存储

全局事务标识符 (GTID) 是创建的唯一标识符,并与在原始服务器(源)上提交的每个事务相关联。此标识符不仅对于它起源的服务器是唯一的,而且在给定复制拓扑中的所有服务器中也是唯一的。

GTID 分配区分在源上提交的客户端事务和在副本上复制的复制事务。当客户端事务在源上提交时,它会被分配一个新的 GTID,前提是该事务已写入二进制日志。保证客户端事务具有单调递增的 GTID,生成的数字之间没有间隙。如果客户端事务未写入二进制日志(例如,因为事务被过滤掉,或者事务是只读的),则不会在源服务器上为其分配 GTID。

复制事务保留分配给源服务器上事务的相同 GTID。GTID 在复制事务开始执行之前就存在,即使复制事务没有写入副本上的二进制日志,或者在副本上被过滤掉,它也会被持久化。MySQL 系统表mysql.gtid_executed用于保存 MySQL 服务器上应用的所有事务的分配 GTID,但存储在当前活动二进制日志文件中的事务除外。

GTID 的自动跳过功能意味着在源上提交的事务只能在副本上应用一次,这有助于保证一致性。一旦在给定服务器上提交了具有给定 GTID 的事务,任何试图执行具有相同 GTID 的后续事务的尝试都会被该服务器忽略。没有引发错误,也没有执行事务中的语句。

如果具有给定 GTID 的事务已开始在服务器上执行,但尚未提交或回滚,则任何在具有相同 GTID 的服务器上启动并发事务的尝试都会被阻止。服务器既不开始执行并发事务也不将控制权返回给客户端。一旦事务的第一次尝试提交或回滚,阻塞在同一 GTID 上的并发会话可能会继续。如果第一次尝试回滚,一个并发会话将继续尝试事务,并且在同一 GTID 上阻塞的任何其他并发会话将保持阻塞状态。如果第一次尝试提交,则所有并发会话停止阻塞,并自动跳过该事务的所有语句。

GTID 表示为一对坐标,由冒号字符 ( :) 分隔,如下所示:

GTID = source_id:transaction_id

source_id标识原始服务器 。通常,源的 server_uuid用于此目的。这transaction_id是一个序列号,由在源上提交事务的顺序确定。例如,要提交的第一个事务的值为1transaction_id而在同一原始服务器上要提交的第十个事务的transaction_id值为 10。事务不可能0在 GTID 中具有序列号。例如,最初在具有 UUID 的服务器上要提交的第 23 个事务 3E11FA47-71CA-11E1-9E33-C80AA9429562具有此 GTID:

3E11FA47-71CA-11E1-9E33-C80AA9429562:23

服务器实例上 GTID 序列号的上限是带符号的 64 位整数的非负值数(2 的 63 次方减去 1,或 9,223,372,036,854,775,807)。如果服务器用完了 GTID,它会采取 指定的操作 binlog_error_action。从 MySQL 8.0.23 开始,当服务器实例接近限制时会发出警告消息。

事务的 GTID 显示在 mysqlbinlog的输出中,它用于标识 Performance Schema 复制状态表中的单个事务,例如, replication_applier_status_by_workergtid_next 系统变量 ( )存储的值@@GLOBAL.gtid_next是单个 GTID。

GTID 集

GTID 集是包含一个或多个单个 GTID 或 GTID 范围的集合。GTID 集在 MySQL 服务器中以多种方式使用。例如, gtid_executedgtid_purged系统变量存储的值是 GTID 集合。( START REPLICA或在 MySQL 8.0.22 之前 START SLAVE)子句UNTIL SQL_BEFORE_GTIDSUNTIL SQL_AFTER_GTIDS可用于使副本处理事务仅达到 GTID 集中的第一个 GTID,或在 GTID 集中的最后一个 GTID 之后停止。内置函数 需要GTID_SUBSET()GTID GTID_SUBTRACT()集作为输入。

来自同一服务器的一系列 GTID 可以折叠成一个表达式,如下所示:

3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5

server_uuid上面的示例表示 第一个到第五个事务源自 MySQL 服务器 3E11FA47-71CA-11E1-9E33-C80AA9429562。来自同一服务器的多个单个 GTID 或 GTID 范围也可以包含在单个表达式中,GTID 或范围用冒号分隔,如以下示例所示:

3E11FA47-71CA-11E1-9E33-C80AA9429562:1-3:11:47-49

GTID 集可以包括单个 GTID 和 GTID 范围的任意组合,并且它可以包括来自不同服务器的 GTID。此示例显示存储在 已应用来自多个源的事务的副本 的gtid_executed系统变量 ( ) 中的 GTID 集:@@GLOBAL.gtid_executed

2174B383-5441-11E8-B90A-C80AA9429562:1-3, 24DA167-0C0C-11E8-8442-00059A3C7B00:1-19

从服务器变量返回 GTID 集时,UUID 按字母顺序排列,数字间隔合并并按升序排列。

GTID 集的语法如下:

gtid_set:
    uuid_set [, uuid_set] ...
    | ''

uuid_set:
    uuid:interval[:interval]...

uuid:
    hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh

h:
    [0-9|A-F]

interval:
    n[-n]

    (n >= 1)
mysql.gtid_executed 表

GTID 存储在数据库中名为 的表 gtid_executedmysql。此表中的一行包含,对于它表示的每个 GTID 或一组 GTID,原始服务器的 UUID,以及该组的开始和结束事务 ID;对于仅引用单个 GTID 的行,最后两个值相同。

mysql.gtid_executed表是在安装或升级 MySQL 服务器时创建的(如果它尚不存在),使用CREATE TABLE 类似于此处显示的语句:

CREATE TABLE gtid_executed (
    source_uuid CHAR(36) NOT NULL,
    interval_start BIGINT(20) NOT NULL,
    interval_end BIGINT(20) NOT NULL,
    PRIMARY KEY (source_uuid, interval_start)
)
警告

与其他 MySQL 系统表一样,不要尝试自己创建或修改此表。

mysql.gtid_executed表供 MySQL 服务器内部使用。当副本上的二进制日志记录被禁用时,它使副本能够使用 GTID,并且当二进制日志丢失时它能够保留 GTID 状态。请注意,mysql.gtid_executed 如果您发出 ,该表将被清除RESET MASTER

mysql.gtid_executed 只有当gtid_modeON或时 ,GTID 才会存储在表中ON_PERMISSIVE。如果二进制日志记录被禁用(log_binis OFF),或者如果 log_replica_updatesor 被禁用,则服务器在事务提交时log_slave_updates将属于每个事务的 GTID 与事务一起存储在 表中。mysql.gtid_executed此外,表以用户可配置的速率定期压缩,如 mysql.gtid_executed 表压缩中所述。

如果启用了二进制日志记录(log_binON),从 MySQL 8.0.17 开始, InnoDB仅用于存储引擎,服务器更新mysql.gtid_executed表的方式与禁用二进制日志记录或副本更新日志记录时相同,在事务提交时存储每个事务的 GTID . 但是,在 MySQL 8.0.17 之前的版本中,对于其他存储引擎,服务器只会 mysql.gtid_executed在二进制日志轮转或服务器关闭时更新表。在这些时候,服务器将写入先前二进制日志的所有事务的 GTID 写入 mysql.gtid_executed桌子。这种情况适用于 MySQL 8.0.17 之前的源,或者 MySQL 8.0.17 之前启用二进制日志记录的副本,或者使用除 以外的存储引擎InnoDB,它具有以下后果:

  • 在服务器意外停止的情况下,当前二进制日志文件中的 GTID 集不会保存在 mysql.gtid_executed表中。这些 GTID 在恢复期间从二进制日志文件添加到表中,以便复制可以继续。例外情况是,如果您在服务器重新启动时禁用二进制日志记录(使用 --skip-log-bin--disable-log-bin)。在这种情况下,服务器无法访问二进制日志文件来恢复 GTID,因此无法启动复制。

  • mysql.gtid_executed表不保存所有已执行事务的 GTID 的完整记录。该信息由gtid_executed 系统变量的全局值提供。在 MySQL 8.0.17 之前的版本和存储引擎不是InnoDB,总是使用@@GLOBAL.gtid_executed,它在每次提交后更新,代表 MySQL 服务器的 GTID 状态,而不是查询 mysql.gtid_executed表。

mysql.gtid_executed即使服务器处于只读或超级只读模式 ,MySQL 服务器也可以写入 表。在 MySQL 8.0.17 之前的版本中,这确保了二进制日志文件仍然可以在这些模式下轮换。如果 mysql.gtid_executed无法访问表进行写入,并且二进制日志文件由于达到最大文件大小 ( max_binlog_size) 以外的任何原因而轮换,则继续使用当前二进制日志文件。一条错误消息将返回到请求轮换的客户端,并在服务器上记录一条警告。如果 mysql.gtid_executed无法访问表进行写入并且max_binlog_size 已达到,则服务器会根据其 binlog_error_action设置进行响应。如果 IGNORE_ERROR设置时,服务器上会记录错误并停止二进制日志记录,或者如果 ABORT_SERVER设置,则服务器关闭。

mysql.gtid_executed 表压缩

随着时间的推移,该 mysql.gtid_executed表可能会充满许多行,这些行引用源自同一服务器的各个 GTID,并且其事务 ID 构成一个范围,类似于此处显示的内容:

+--------------------------------------+----------------+--------------+
| source_uuid                          | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37             | 37           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 38             | 38           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 39             | 39           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 40             | 40           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 41             | 41           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 42             | 42           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 43             | 43           |
...

为了节省空间,MySQL 服务器可以 mysql.gtid_executed通过用跨越整个事务标识符间隔的单个行替换每个这样的行集来定期压缩表,如下所示:

+--------------------------------------+----------------+--------------+
| source_uuid                          | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37             | 43           |
...

服务器可以使用名为 的专用前台线程执行压缩 thread/sql/compress_gtid_table。该线程未列在 的输出中SHOW PROCESSLIST,但可以将其视为 threads表中的一行,如下所示:

mysql> SELECT * FROM performance_schema.threads WHERE NAME LIKE '%gtid%'\G
*************************** 1. row ***************************
          THREAD_ID: 26
               NAME: thread/sql/compress_gtid_table
               TYPE: FOREGROUND
     PROCESSLIST_ID: 1
   PROCESSLIST_USER: NULL
   PROCESSLIST_HOST: NULL
     PROCESSLIST_DB: NULL
PROCESSLIST_COMMAND: Daemon
   PROCESSLIST_TIME: 1509
  PROCESSLIST_STATE: Suspending
   PROCESSLIST_INFO: NULL
   PARENT_THREAD_ID: 1
               ROLE: NULL
       INSTRUMENTED: YES
            HISTORY: YES
    CONNECTION_TYPE: NULL
       THREAD_OS_ID: 18677

在服务器上启用二进制日志记录时,不使用这种压缩方法,而是 mysql.gtid_executed在每次二进制日志轮换时压缩表。但是,当在服务器上禁用二进制日志记录时, thread/sql/compress_gtid_table线程会休眠直到执行了指定数量的事务,然后唤醒以执行 mysql.gtid_executed表的压缩。然后它会休眠直到发生相同数量的事务,然后醒来再次执行压缩,无限期地重复此循环。压缩表之前经过的事务数,以及压缩率,由 gtid_executed_compression_period 系统变量。将该值设置为 0 意味着线程永远不会唤醒,这意味着不使用这种显式压缩方法。相反,压缩会根据需要隐式发生。

从 MySQL 8.0.17 开始,事务由一个单独的进程InnoDB 写入 表,而不是非事务。这个过程由不同的线程控制, . 这个 GTID 持久化线程按组收集 GTID,将它们刷新到 表中,然后压缩表。如果服务器混合了 事务和非事务,它们分别写入表中,则线程执行的压缩 会干扰 GTID 持久化线程的工作,并可能显着降低速度。因此,从该版本开始,建议您设置 mysql.gtid_executedInnoDBinnodb/clone_gtid_threadmysql.gtid_executedInnoDBInnoDBmysql.gtid_executedcompress_gtid_tablegtid_executed_compression_period 为 0,这样compress_gtid_table线程永远不会被激活。

从 MySQL 8.0.23 开始, gtid_executed_compression_period 默认值为 0,事务InnoDB和非事务都由GTID 持久化线程 InnoDB写入 表。mysql.gtid_executed

对于 MySQL 8.0.17 之前的版本, gtid_executed_compression_period 可以使用默认值 1000,这意味着每 1000 个事务后对表进行压缩,或者您可以选择替代值。在这些版本中,如果您将值设置为 0 并禁用二进制日志记录,则不会对mysql.gtid_executed表执行显式压缩,并且您应该准备好在以下情况下表可能需要的磁盘空间量可能会大幅增加你来做这件事。

当服务器实例启动时,如果 gtid_executed_compression_period 设置为非零值并启动 thread/sql/compress_gtid_table线程,在大多数服务器配置中,会对mysql.gtid_executed表执行显式压缩。在启用二进制日志记录的 MySQL 8.0.17 之前的版本中,压缩是由二进制日志在启动时轮换的事实触发的。在 MySQL 8.0.20 版本中,压缩由线程启动触发。在中间版本中,压缩不会在启动时进行。