全局事务标识符 (GTID) 是一个唯一标识符,在源服务器(来源)上创建并与每个已提交事务关联。此标识符不仅对源服务器是唯一的,而且对给定复制拓扑中的所有服务器都是唯一的。
GTID 分配区分了在源上提交的客户端事务和在副本上复制的复制事务。当客户端事务在源上提交时,会为其分配一个新的 GTID,前提是该事务已写入二进制日志。客户端事务保证具有单调递增的 GTID,且生成的数字之间没有间隙。如果客户端事务未写入二进制日志(例如,因为该事务已过滤掉,或该事务是只读的),则在源服务器上不会为其分配 GTID。
复制事务保留在源服务器上为该事务分配的相同 GTID。在复制事务开始执行之前,GTID 已存在,即使复制事务未写入副本上的二进制日志,或在副本上被过滤掉,它也会被保留。mysql.gtid_executed
系统表用于保留在 MySQL 服务器上应用的所有事务的已分配 GTID,但当前活动二进制日志文件中存储的 GTID 除外。
GTID 的自动跳过功能意味着在源上提交的事务最多只能在副本上应用一次,这有助于保证一致性。一旦在给定服务器上提交了具有给定 GTID 的事务,该服务器将忽略任何尝试执行具有相同 GTID 的后续事务。不会引发错误,并且不会执行事务中的任何语句。
如果具有给定 GTID 的事务已在服务器上开始执行,但尚未提交或回滚,则任何尝试在服务器上使用相同 GTID 启动并发事务都会被阻止。服务器既不会开始执行并发事务,也不会将控制权返回给客户端。一旦对事务的首次尝试提交或回滚,则在同一 GTID 上阻塞的并发会话可能会继续进行。如果首次尝试回滚,则一个并发会话将继续尝试该事务,并且任何在同一 GTID 上阻塞的其他并发会话仍将被阻塞。如果首次尝试提交,则所有并发会话都会停止被阻塞,并自动跳过事务的所有语句。
GTID 表示为一对坐标,用冒号字符 (:
) 分隔,如下所示
GTID = source_id:transaction_id
source_id
标识源服务器。通常,源的 server_uuid
用于此目的。transaction_id
是由事务在源上提交的顺序确定的序列号。例如,要提交的第一个事务的transaction_id
为 1
,而在同一源服务器上要提交的第十个事务分配的transaction_id
为 10
。在 GTID 中,事务不可能以 0
作为序列号。例如,在 UUID 为 3E11FA47-71CA-11E1-9E33-C80AA9429562
的服务器上最初要提交的第 23 个事务具有此 GTID
3E11FA47-71CA-11E1-9E33-C80AA9429562:23
服务器实例上 GTID 的序列号上限是带符号 64 位整数的非负值数(263 - 1
,或 9223372036854775807
)。如果服务器耗尽 GTID,它将执行 binlog_error_action
指定的操作。当服务器实例接近限制时,会发出警告消息。
MySQL 9.0 还支持标记 GTID。标记 GTID 由三部分组成,由冒号字符分隔,如下所示
GTID = source_id:tag:transaction_id
在这种情况下,source_id
和 transaction_id
与前面定义的相同。tag
是用于标识特定事务组的用户定义字符串;有关允许的语法,请参阅 gtid_next
系统变量的说明。示例:在 UUID 为 ed102faf-eb00-11eb-8f20-0c5415bfaa1d
且标记为 Domain_1
的服务器上最初要提交的第 117 个事务具有此 GTID
ed102faf-eb00-11eb-8f20-0c5415bfaa1d:Domain_1:117
事务的 GTID 显示在 mysqlbinlog 的输出中,并且用于在性能模式复制状态表中标识单个事务,例如 replication_applier_status_by_worker
。gtid_next
系统变量 (@@GLOBAL.gtid_next
) 存储的值是单个 GTID。
GTID 集是包含一个或多个单个 GTID 或 GTID 范围的集合。GTID 集在 MySQL 服务器中以多种方式使用。例如,gtid_executed
和 gtid_purged
系统变量存储的值是 GTID 集。START REPLICA
选项 UNTIL SQL_BEFORE_GTIDS
和 UNTIL SQL_AFTER_GTIDS
可用于使副本仅处理 GTID 集中的第一个 GTID 之前的事务,或在 GTID 集中的最后一个 GTID 之后停止处理。内置函数 GTID_SUBSET()
和 GTID_SUBTRACT()
需要 GTID 集作为输入。
源自同一服务器的 GTID 范围可以折叠成一个单独的表达式,如下所示
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
以上示例表示源自 MySQL 服务器的第一个到第五个事务,该服务器的 server_uuid
为 3E11FA47-71CA-11E1-9E33-C80AA9429562
。源自同一服务器的多个单个 GTID 或 GTID 范围也可以包含在单个表达式中,GTID 或范围用冒号分隔,如下例所示
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-3:11:47-49
GTID 集可以包含任何单个 GTID 组合和 GTID 范围,并且可以包含源自不同服务器的 GTID。此示例显示存储在 gtid_executed
系统变量 (@@GLOBAL.gtid_executed
) 中的 GTID 集,该变量属于已应用来自多个来源的事务的副本
2174B383-5441-11E8-B90A-C80AA9429562:1-3, 24DA167-0C0C-11E8-8442-00059A3C7B00:1-19
当从服务器变量返回 GTID 集时,UUID 按字母顺序排列,数字间隔合并并按升序排列。
在构建 GTID 集时,用户定义的标记被视为 UUID 的一部分。这意味着源自同一服务器且具有相同标记的多个 GTID 可以包含在单个表达式中,如本示例所示
3E11FA47-71CA-11E1-9E33-C80AA9429562:Domain_1:1-3:11:47-49
源自同一服务器但具有不同标记的 GTID 的处理方式类似于源自不同服务器的 GTID,如下所示
3E11FA47-71CA-11E1-9E33-C80AA9429562:Domain_1:1-3:15-21, 3E11FA47-71CA-11E1-9E33-C80AA9429562:Domain_2:8-52
GTID 集的完整语法如下
gtid_set:
uuid_set [, uuid_set] ...
| ''
uuid_set:
uuid:[tag:]interval[:interval]...
uuid:
hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh
h:
[0-9|A-F]
tag:
[a-z_][a-z0-9_]{0,31}
interval:
m[-n]
(m >= 1; n > m)
GTID 存储在名为 gtid_executed
的表中,位于 mysql
数据库中。此表中的一行包含它所表示的每个 GTID 或 GTID 集的源服务器的 UUID、用户定义的标记(如果有)、以及该集的起始和结束事务 ID;对于仅引用单个 GTID 的行,后两个值相同。
当安装或升级 MySQL Server 时,将使用类似于此处所示的 CREATE TABLE
语句创建 mysql.gtid_executed
表(如果它不存在)
CREATE TABLE gtid_executed (
source_uuid CHAR(36) NOT NULL,
interval_start BIGINT NOT NULL,
interval_end BIGINT NOT NULL,
gtid_tag CHAR(32) NOT NULL,
PRIMARY KEY (source_uuid, gtid_tag, interval_start)
);
与其他 MySQL 系统表一样,不要尝试自己创建或修改此表。
表 mysql.gtid_executed
由 MySQL 服务器供内部使用。当从服务器上禁用二进制日志记录时,它使从服务器能够使用 GTID,并且当二进制日志丢失时,它使 GTID 状态能够保留。请注意,如果发出 RESET BINARY LOGS AND GTIDS
,则表 mysql.gtid_executed
将被清除。
仅当 gtid_mode
为 ON
或 ON_PERMISSIVE
时,GTID 才会存储在表 mysql.gtid_executed
中。如果禁用二进制日志记录(log_bin
为 OFF
),或者如果禁用 log_replica_updates
,则服务器在事务提交时将属于每个事务的 GTID 与事务一起存储在缓冲区中,并且后台线程会定期将缓冲区的内容作为一条或多条记录添加到表 mysql.gtid_executed
中。此外,该表会定期以用户可配置的速率进行压缩,如 mysql.gtid_executed 表压缩 中所述。
如果启用二进制日志记录(log_bin
为 ON
),则仅对于 InnoDB
存储引擎,服务器以与禁用二进制日志记录或从服务器更新日志记录时相同的方式更新表 mysql.gtid_executed
,在事务提交时存储每个事务的 GTID。对于其他存储引擎,服务器仅在二进制日志轮换或服务器关闭时更新表 mysql.gtid_executed
。在这些时间,服务器将所有写入到前一个二进制日志中的事务的 GTID 写入到表 mysql.gtid_executed
中。
如果无法写入表 mysql.gtid_executed
,并且由于达到最大文件大小 (max_binlog_size
) 以外的任何原因而轮换二进制日志文件,则继续使用当前二进制日志文件。向请求轮换的客户端返回错误消息,并在服务器上记录警告。如果无法写入表 mysql.gtid_executed
并且达到 max_binlog_size
,则服务器会根据其 binlog_error_action
设置进行响应。如果设置 IGNORE_ERROR
,则在服务器上记录错误并停止二进制日志记录,或者如果设置 ABORT_SERVER
,则服务器关闭。
随着时间的推移,表 mysql.gtid_executed
中可能包含许多行,这些行引用源自同一服务器、具有相同 GTID 标记(如果存在)并且其事务 ID 构成一个范围的各个 GTID,类似于此处所示
+--------------------------------------+----------------+--------------+----------+
| source_uuid | interval_start | interval_end | gtid_tag |
|--------------------------------------+----------------+--------------|----------+
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 31 | 31 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 32 | 32 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 33 | 33 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 34 | 34 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 35 | 35 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 36 | 36 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37 | 37 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 38 | 38 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 39 | 39 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 40 | 40 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 41 | 41 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 42 | 42 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 43 | 43 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 44 | 44 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 45 | 45 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 46 | 46 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 47 | 47 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 48 | 48 | Domain_1 |
...
为了节省空间,MySQL 服务器可以通过用一行跨越整个事务标识符间隔的行替换每一组此类行来定期压缩表 mysql.gtid_executed
,如下所示
+--------------------------------------+----------------+--------------+----------+
| source_uuid | interval_start | interval_end | gtid_tag |
|--------------------------------------+----------------+--------------|----------+
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 31 | 35 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 36 | 39 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 40 | 43 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 44 | 46 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 47 | 48 | Domain_1 |
...
服务器可以使用名为 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 意味着线程永远不会唤醒,这意味着不会使用此显式压缩方法。相反,压缩会根据需要隐式发生。
InnoDB
事务由一个独立于用于涉及除 InnoDB
之外的存储引擎的事务的进程写入 mysql.gtid_executed
表。此进程由另一个线程 innodb/clone_gtid_thread
控制。此 GTID 持久化线程收集组中的 GTID,将它们刷新到 mysql.gtid_executed
表,然后压缩该表。如果服务器同时包含 InnoDB
事务和非 InnoDB
事务(它们分别写入 mysql.gtid_executed
表),则 compress_gtid_table
线程执行的压缩会干扰 GTID 持久化线程的工作,并可能显著降低其速度。因此,建议将 gtid_executed_compression_period
设置为 0,以便永远不会激活 compress_gtid_table
线程。
gtid_executed_compression_period
的默认值为 0,并且所有事务(无论存储引擎如何)都由 GTID 持久化线程写入 mysql.gtid_executed
表。
当启动服务器实例时,如果将 gtid_executed_compression_period
设置为非零值并启动 thread/sql/compress_gtid_table
线程,则在大多数服务器配置中,将对 mysql.gtid_executed
表执行显式压缩。压缩由线程启动触发。