全局事务标识符 (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
是一个序列号,由在源上提交事务的顺序确定。例如,要提交的第一个事务的值为1
,
transaction_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_worker
。gtid_next
系统变量 ( )存储的值@@GLOBAL.gtid_next
是单个 GTID。
GTID 集是包含一个或多个单个 GTID 或 GTID 范围的集合。GTID 集在 MySQL 服务器中以多种方式使用。例如,
gtid_executed
和
gtid_purged
系统变量存储的值是 GTID 集合。(
START
REPLICA
或在 MySQL 8.0.22 之前
START
SLAVE
)子句UNTIL
SQL_BEFORE_GTIDS
和UNTIL
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)
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(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_mode
是
ON
或时
,GTID 才会存储在表中ON_PERMISSIVE
。如果二进制日志记录被禁用(log_bin
is
OFF
),或者如果
log_replica_updates
or
被禁用,则服务器在事务提交时log_slave_updates
将属于每个事务的 GTID 与事务一起存储在
表中。mysql.gtid_executed
此外,表以用户可配置的速率定期压缩,如
mysql.gtid_executed 表压缩中所述。
如果启用了二进制日志记录(log_bin
是
ON
),从 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
表可能会充满许多行,这些行引用源自同一服务器的各个 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_executed
InnoDB
innodb/clone_gtid_thread
mysql.gtid_executed
InnoDB
InnoDB
mysql.gtid_executed
compress_gtid_table
gtid_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 版本中,压缩由线程启动触发。在中间版本中,压缩不会在启动时进行。