全局事务标识符(GTID)是服务器源服务器上提交的每个事务的唯一标识符。这标识符不仅在源服务器上是唯一的,还在整个复制拓扑结构中的所有服务器上都是唯一的。
GTID 分配区分了客户端事务和复制事务。客户端事务是在源服务器上提交的,而复制事务是在副本服务器上重现的。当客户端事务在源服务器上提交时,它将被分配一个新的 GTID,只要事务被写入二进制日志中。客户端事务保证具有单调递增的 GTID,且没有间隙。如果客户端事务没有写入二进制日志(例如,因为事务被过滤掉了,或者事务是只读的),那么它在源服务器上不会被分配 GTID。
复制事务保留了在源服务器上分配的相同 GTID。GTID 在复制事务开始执行之前就存在,即使复制事务没有写入副本服务器上的二进制日志,或者在副本服务器上被过滤掉。系统表 mysql.gtid_executed
用于保留所有应用于 MySQL 服务器的交易的分配 GTID,除了当前活动的二进制日志文件中的交易。
GTID 的自动跳过功能意味着源服务器上的事务在副本服务器上最多只能应用一次,这有助于保证一致性。一旦事务在给定服务器上提交,它的 GTID 就会被忽略,任何尝试执行相同 GTID 的后续事务都会被忽略。不会抛出错误,也不会执行事务中的任何语句。
如果事务已经开始在服务器上执行,但尚未提交或回滚,那么任何尝试在服务器上启动具有相同 GTID 的并发事务都会被阻塞。服务器既不开始执行并发事务,也不返回控制权给客户端。一旦第一个事务尝试提交或回滚,阻塞的并发会话可以继续。如果第一个尝试回滚了,一个并发会话将尝试事务,而其他阻塞的并发会话将继续阻塞。如果第一个尝试提交了,所有阻塞的并发会话将停止阻塞,并自动跳过事务的所有语句。
GTID 表示为一对坐标,使用冒号字符 (:
) 分隔,如下所示:
GTID = source_id:transaction_id
源标识符 source_id
标识源服务器。通常,源服务器的 server_uuid
用于此目的。transaction_id
是根据事务在源服务器上提交的顺序确定的序列号。例如,第一个提交的事务的 transaction_id
是 1
,而第十个提交的事务的 transaction_id
是 10
。不可能有事务的序列号为 0
。例如,源服务器 UUID 为 3E11FA47-71CA-11E1-9E33-C80AA9429562
的第 23 个提交的事务的 GTID 是:
3E11FA47-71CA-11E1-9E33-C80AA9429562:23
服务器实例的序列号上限为 signed 64 位整数的非负值 (263 - 1
,或 9223372036854775807
)。如果服务器用完了 GTID,将执行指定的 binlog_error_action
操作。当服务器实例接近限制时,将发出警告消息。
MySQL 8.3 还支持标记 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 范围也可以包含在一个表达式中,以冒号分隔,如下所示:
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-3:11:47-49
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 被视为来自不同服务器,类似于以下示例:
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
数据库中。该表中的每一行包含来自服务器的 UUID、用户定义的标签(如果有)和事务 ID 集的起始和结束值;对于仅引用单个 GTID 的行,这两个值相同。
mysql.gtid_executed 表是在 MySQL 服务器安装或升级时创建的(如果不存在),使用类似于以下的 CREATE TABLE
语句:
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.gtid_executed 表是 MySQL 服务器内部使用的。它使副本能够在二进制日志禁用时使用 GTID,并在二进制日志丢失时保留 GTID 状态。请注意,mysql.gtid_executed 表将在您发出 RESET BINARY LOGS AND GTIDS
时被清除。
GTIDs 只在 mysql.gtid_executed
表中存储,当 gtid_mode
设置为 ON
或 ON_PERMISSIVE
时。如果二进制日志记录被禁用 (log_bin
是 OFF
),或者如果 log_replica_updates
被禁用,服务器将事务的 GTID 与事务一起存储在缓冲区中,当事务提交时,并且背景线程定期将缓冲区的内容作为一个或多个条目添加到 mysql.gtid_executed
表中。此外,该表还会根据用户可配置的速率进行周期性压缩,如 mysql.gtid_executed 表压缩 中所述。
如果二进制日志记录被启用 (log_bin
是 ON
),对于 InnoDB
存储引擎,只有在事务提交时,服务器才会更新 mysql.gtid_executed
表,类似于二进制日志记录或副本更新日志记录被禁用的情况。对于其他存储引擎,服务器只在二进制日志文件旋转或服务器关闭时更新 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
表,通过将每个这样的行集替换为一个单行,跨越整个事务 ID 区间,类似于这里所示:
+--------------------------------------+----------------+--------------+----------+
| 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
线程将睡眠,直到指定数量的事务被执行,然后醒来执行压缩,然后睡眠,直到相同数量的事务被执行,然后醒来执行压缩,如此循环下去。压缩率由 gtid_executed_compression_period
系统变量控制。将该值设置为 0 意味着该线程永远不会醒来,这意味着该压缩方法不会被使用。相反,压缩将隐式地发生,如需要。
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
表进行显式压缩。压缩由线程启动触发。