16.1.3.1 GTID 格式和存储

全局事务标识符(GTID)是创建的唯一标识符,并且与在源服务器(源)上提交的每个事务相关联。该标识符不仅对于它起源的服务器是唯一的,而且在给定复制拓扑中的所有服务器上也是唯一的。

GTID 分配区分在源上提交的 Client 端事务和在副本上重现的复制事务。如果将 Client 端事务提交到源上,则将为其分配一个新的 GTID,前提是该事务已写入二进制日志中。保证 ClientTransaction 具有单调增加的 GTID,而在生成的数字之间没有间隙。如果没有将 Client 事务写入二进制日志(例如,由于该事务已被滤除或该事务为只读),则不会在原始服务器上为其分配 GTID。

复制的事务保留与原始服务器上分配给该事务的 GTID 相同的 GTID。 GTID 在复制事务开始执行之前就存在,并且即使复制事务未写入副本上的二进制日志或在副本上被过滤掉,GTID 也会保留。 MySQL 系统 tablemysql.gtid_executed用于保留 MySQL 服务器上应用的所有事务(已存储在当前活动的二进制日志文件中的事务除外)的已分配 GTID。

GTID 的自动跳过功能意味着在源上提交的事务只能在副本上应用一次,这有助于确保一致性。一旦将具有给定 GTID 的事务提交到给定服务器上,则该服务器将忽略执行具有相同 GTID 的后续事务的任何尝试。不会引发任何错误,并且不会执行事务中的任何语句。

如果具有给定 GTID 的事务已开始在服务器上执行,但尚未提交或回滚,则在具有相同 GTID 的服务器上启动并发事务的任何尝试都将被阻止。服务器既不开始执行并发事务,也没有将控制权返回给 Client 端。一旦对事务的第一次尝试提交或回滚,在同一 GTID 上阻塞的并发会话可能会 continue 进行。如果第一次尝试回滚,则一个并发会话将 continue 尝试事务,并且在同一 GTID 上阻塞的任何其他并发会话将保持阻塞状态。如果进行了第一次尝试,则所有并发会话都将停止被阻止,并自动跳过事务的所有语句。

GTIDtable 示为一对坐标,由冒号(:)分隔,如下所示:

GTID = source_id:transaction_id
  • source_id *标识原始服务器。通常,源的server_uuid用于此目的。 * transaction_id *是由事务在源上提交的 Sequences 确定的序列号。例如,要提交的第一个事务的_ transaction_id 1,并且要在同一原始服务器上提交的第十个事务的10分配为 transaction_id *。事务不可能在 GTID 中使用0作为序列号。例如,最初要在服务器上使用 UUID 3E11FA47-71CA-11E1-9E33-C80AA9429562提交的第二十三笔 Transaction 具有以下 GTID:
3E11FA47-71CA-11E1-9E33-C80AA9429562:23

事务的 GTID 显示在mysqlbinlog的输出中,用于在性能模式复制状态 table(例如replication_applier_status_by_worker)中标识单个事务。 gtid_next系统变量(@@GLOBAL.gtid_next)存储的值是单个 GTID。

GTID Sets

GTID 集是包括一个或多个单个 GTID 或一系列 GTID 的集。 GTID 集在 MySQL 服务器中以多种方式使用。例如,由gtid_executedgtid_purged系统变量存储的值是 GTID 集。 START SLAVE子句UNTIL SQL_BEFORE_GTIDSUNTIL SQL_AFTER_GTIDS可以用于使副本处理事务最多直到 GTID 集中的第一个 GTID,或者在 GTID 集中的最后一个 GTID 之后停止。内置函数GTID_SUBSET()GTID_SUBTRACT()需要 GTID 集作为 Importing。

可以将源自同一服务器的一系列 GTID 折叠为一个 table 达式,如下所示:

3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5

上面的示例 table 示源自server_uuid3E11FA47-71CA-11E1-9E33-C80AA9429562的 MySQL 服务器的第一至第五个事务。源自同一服务器的多个单个 GTID 或 GTID 范围也可以包含在单个 table 达式中,且 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 按字母 Sequences 排列,并且数字间隔按升序合并。

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)
mysql.gtid_executed Table

GTID 存储在mysql数据库中名为gtid_executed的 table 中。该 table 中的一行包含它代 table 的每个 GTID 或一组 GTID,始发服务器的 UUID 以及该组的开始和结束事务 ID。对于仅引用单个 GTID 的行,最后两个值相同。

在安装或升级 MySQL Server 时,将使用类似于以下所示的CREATE TABLE语句创建mysql.gtid_executedtable(如果尚不存在):

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)
)

Warning

与其他 MySQL 系统 table 一样,请勿尝试自己创建或修改该 table。

mysql.gtid_executedtable 提供给 MySQL 服务器内部使用。当禁用副本上的二进制日志记录时,它使副本可以使用 GTID,而当二进制日志丢失时,它可以保留 GTID 状态。请注意,如果发出RESET MASTER,则会清除mysql.gtid_executedtable。

仅当gtid_modeONON_PERMISSIVE时,GTID 才会存储在mysql.gtid_executedtable 中。 GTID 的存储点取决于是否启用了二进制日志记录:

  • 如果禁用了二进制日志记录(log_binOFF),或者如果禁用了log_slave_updates,则服务器将属于每个事务的 GTID 与事务一起存储在 table 中。此外,该 table 以用户可配置的速率定期压缩;有关更多信息,请参见mysql.gtid_exectedtable 压缩。这种情况仅适用于禁用了二进制日志记录或副本更新日志记录的副本。它不适用于复制源服务器,因为必须在源上启用二进制日志记录才能进行复制。

  • 如果启用了二进制日志记录(log_binON),则每当旋转二进制日志或关闭服务器时,服务器会将写入先前的二进制日志的所有事务的 GTID 写入mysql.gtid_executedtable。这种情况适用于复制源服务器或启用了二进制日志记录的副本。

如果服务器意外停止,则当前二进制日志文件中的 GTID 集不会保存在mysql.gtid_executedtable 中。在恢复期间,这些 GTID 从二进制日志文件添加到 table 中。exception 是重新启动服务器时未启用二进制日志记录。在这种情况下,服务器无法访问二进制日志文件以恢复 GTID,因此无法启动复制。

启用二进制日志记录后,mysql.gtid_executedtable 不会保存所有已执行事务的 GTID 的完整记录。该信息由gtid_executed系统变量的全局值提供。始终使用@@GLOBAL.gtid_executed来 table 示 MySQL 服务器的 GTID 状态,该值在每次提交后都会更新,并且不要查询mysql.gtid_executedtable。

mysql.gtid_executetable 压缩

随着时间的流逝,mysql.gtid_executedtable 可能会充满很多行,这些行引用源自同一服务器的各个 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_executedtable,如下所示:

+--------------------------------------+----------------+--------------+
| source_uuid                          | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37             | 43           |
...

通过设置gtid_executed_compression_period系统变量,可以控制在压缩 table 之前允许经过的事务数,从而控制压缩率。此变量的默认值为 1000,这意味着默认情况下,每 1000 个事务处理后将压缩 table。将gtid_executed_compression_period设置为 0 根本无法执行压缩,并且如果这样做,您应该为gtid_executedtable 可能需要的磁盘空间大量增加做好准备。

Note

启用二进制日志记录后,将不使用gtid_executed_compression_period的值,并且在每次二进制日志旋转时将压缩mysql.gtid_executedtable。

mysql.gtid_executedtable 的压缩由名为thread/sql/compress_gtid_table的专用前台线程执行。该线程未在SHOW PROCESSLIST的输出中列出,但可以在threadstable 中被视为一行,如下所示:

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

thread/sql/compress_gtid_table线程通常会休眠,直到执行了gtid_executed_compression_period个事务为止,然后如前所述唤醒以执行mysql.gtid_executedtable 的压缩。然后休眠,直到发生另外gtid_executed_compression_period个事务,然后再次唤醒以执行压缩,并无限期重复此循环。禁用二进制日志记录时将此值设置为 0 意味着线程始终处于休眠状态,并且永远不会唤醒。