19.1.3.1 GTID 格式和存储
GTID 是一个唯一标识符,用于标识每个在服务器源上提交的事务。这标识符不仅在服务器上唯一,还在整个复制拓扑结构中唯一。
GTID 分配区分客户端事务和复制事务。客户端事务在源服务器上提交时,分配一个新的 GTID,条件是事务已经写入到二进制日志中。客户端事务的 GTID 是递增的,且没有间隙。如果客户端事务没有写入到二进制日志中(例如,因为事务被过滤或事务是只读的),那么在源服务器上不分配 GTID。
复制事务保留了在源服务器上分配的同一个 GTID。GTID 在复制事务开始执行前存在,并且在复制事务未写入到二进制日志中或在复制服务器上被过滤时也存在。系统表 mysql.gtid_executed
用于保存所有在 MySQL 服务器上应用的事务的分配 GTID,除了当前活动二进制日志文件中的事务。
GTID 自动跳过功能意味着在源服务器上提交的事务在复制服务器上只能被应用一次,这有助于确保一致性。已经提交的事务的 GTID 在服务器上不能被再次执行,不会出现错误,也不会执行事务中的语句。
如果在服务器上已经开始执行的事务,但还没有提交或回滚,任何尝试在同一个 GTID 上执行的并发事务都会被阻塞。服务器不会开始执行并发事务,也不会返回控制权给客户端。直到第一个尝试的事务提交或回滚,阻塞的并发会话可以继续。 如果第一个尝试的事务回滚,一个并发会话可以继续尝试事务,而其他阻塞在同一个 GTID 上的并发会话仍然被阻塞。如果第一个尝试的事务提交,所有阻塞的并发会话都停止,并且自动跳过事务的所有语句。
GTID 由一对坐标组成,使用冒号字符(:
)分隔,例如:
GTID = source_id:transaction_id
源服务器的标识符是 source_id
,通常使用服务器的 UUID。事务标识符是根据事务在源服务器上提交的顺序确定的。例如,第一个提交的事务的 transaction_id
是 1
,第十个提交的事务的 transaction_id
是 10
。不能有事务拥有 0
的顺序号。例如,在服务器 UUID 为 3E11FA47-71CA-11E1-9E33-C80AA9429562
的服务器上提交的第 23 个事务的 GTID 是:
3E11FA47-71CA-11E1-9E33-C80AA9429562:23
MySQL 服务器实例的 GTID 序列号上限是有符号 64 位整数的非负值(263 - 1
,或9223372036854775807
)。如果服务器耗尽 GTID,它将执行由binlog_error_action
指定的操作。服务器实例 approaching the limit 时将发出警告信息。
MySQL 8.4 还支持标记 GTID。标记 GTID 由三个部分组成,使用冒号字符分隔,如下所示:
GTID = source_id:tag:transaction_id
在这个例子中,source_id
和 transaction_id
是之前定义的。tag
是用户定义的字符串,用于标识特定事务组;请参阅gtid_next
系统变量的描述,了解允许的语法。 Example:原先在服务器 ed102faf-eb00-11eb-8f20-0c5415bfaa1d
上提交的第 117 个事务,使用标记 Domain_1
,其 GTID 如下所示:
ed102faf-eb00-11eb-8f20-0c5415bfaa1d:Domain_1:117
事务的 GTID 在mysqlbinlog的输出中显示,并用于在性能 Schema 复制状态表中标识单个事务,例如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。这一示例显示了一个副本中存储在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.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 系统表一样,不要尝试创建或修改该表自己。
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 Table Compression。
如果启用二进制日志(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
表可能会被填充许多行,指向同一服务器上的 GTIDs,具有相同的 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
交易写入 mysql.gtid_executed
表的过程与其他存储引擎的交易不同。这个过程由不同的线程 innodb/clone_gtid_thread
控制。这条 GTID 持久化线程将 GTIDs 分组,flush 到 mysql.gtid_executed
表,然后压缩该表。如果服务器同时包含 InnoDB
交易和非 InnoDB
交易,写入 mysql.gtid_executed
表的压缩操作将干扰 GTID 持久化线程的工作,可能会导致性能下降。因此,建议将gtid_executed_compression_period
设置为 0,以避免 compress_gtid_table
线程的激活。
默认值为 gtid_executed_compression_period
,所有交易无论存储引擎都写入 mysql.gtid_executed
表的过程由 GTID 持久化线程控制。
当服务器实例启动时,如果gtid_executed_compression_period
设置为非零值,并且thread/sql/compress_gtid_table
线程被启动,在大多数服务器配置中,对mysql.gtid_executed
表进行明确压缩。压缩由线程启动触发。