全局事务标识符 (GTID) 是一个唯一标识符,由源服务器创建并与在该服务器上提交的每个事务相关联。此标识符不仅对它所在的服务器唯一,而且对给定复制拓扑中的所有服务器唯一。
GTID 分配区分客户端事务(在源服务器上提交)和复制事务(在副本上复制)。当客户端事务在源服务器上提交时,如果该事务被写入二进制日志,则会为其分配一个新的 GTID。客户端事务的 GTID 具有单调递增且无间隙的特性。如果客户端事务未写入二进制日志(例如,因为事务被过滤掉了或事务是只读的),则它不会在源服务器上分配 GTID。
复制事务保留与在源服务器上分配给事务相同的 GTID。GTID 在复制事务开始执行之前存在,并且即使复制事务未写入副本的二进制日志或在副本上被过滤掉,也会持久保存。 mysql.gtid_executed
系统表用于保存所有应用于 MySQL 服务器的事务的分配的 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
的服务器上最初提交的第二十三笔事务具有此 GTID
3E11FA47-71CA-11E1-9E33-C80AA9429562:23
服务器实例上 GTID 的序列号上限为带符号 64 位整数的非负值数量 (263 - 1
,或 9223372036854775807
)。如果服务器用完了 GTID,它将采取由 binlog_error_action
指定的操作。当服务器实例接近限制时,会发出警告消息。
MySQL 8.4 还支持带标签的 GTID。带标签的 GTID 由三个部分组成,用冒号字符分隔,如下所示
GTID = source_id:tag:transaction_id
在这种情况下,source_id
和 transaction_id
与前面定义的相同。 tag
是一个用户定义的字符串,用于标识特定的事务组;有关允许的语法的描述,请参阅 gtid_next
系统变量。 示例:最初提交到 UUID 为 ed102faf-eb00-11eb-8f20-0c5415bfaa1d
的服务器上的第一百一十七笔事务,标签为 Domain_1
,具有此 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
上面的示例表示来自 server_uuid
为 3E11FA47-71CA-11E1-9E33-C80AA9429562
的 MySQL 服务器的第一到第五笔事务。来自同一服务器的多个单个 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 服务器安装或升级时,使用类似于此处所示的 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,具有相同的 GTID 标签(如果有),并且其事务 ID 构成一个范围,类似于此处所示的内容
+--------------------------------------+----------------+--------------+----------+
| 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
表执行显式压缩。压缩由线程启动触发。