MySQL
MySQL高可用配置
MySQL Slave状态参数详解
基于二进制日志文件的复制
使用全局事务标识符GTID复制
MySQL安装升级
MySQL RPM包安装参考
MySQL编译化安装参考
MySQL Server版本升级
MySQL 操作手册/说明
MySQL导入导出操作
库,表,字段的字符集修改方法
根据字段生成自定义SQL语句
MySQL查询流程概述
MySQL 常用脚本
MySQL数据库冷备脚本
MySQL内存占用分析
MySQL内存消耗分析
检查MySQL引起的高内存占用
InnoDB下的内存分析和优化计算
MySQL常见问题
MySQL告警:Aborted connection日志的分析
MySQL使用命令kill进程后出现killed死锁问题
从库重起初始化relaylog失败
安全插件Connection-Control导致无法登录的问题
MySQL性能优化
MySQL查询慢性能分析
本文档使用 MrDoc 发布
-
+
home page
使用全局事务标识符GTID复制
## 1、GTID 概述 GTID的全称是global transaction id,表示的是全局事务标识符。 GTID的分配方式为`uuid:trans_id`,其中: - uuid是每个mysql服务器都唯一的,记录在$datadir/auto.cnf中。如果复制结构中,任意两台服务器uuid重复,在启动复制功能时会报错。可删除auto.conf文件后重启mysql实例解决。 - trans_id是事务ID,可以唯一标记某MySQL服务器上执行的某个事务。事务号从1开始,每提交一个事务,事务号加1。 ```sql mysql> show variables like "%uuid%"; +---------------+--------------------------------------+ | Variable_name | Value | +---------------+--------------------------------------+ | server_uuid | a126fcb6-3706-11e8-b1d5-000c294ebf0d | +---------------+--------------------------------------+ 1 row in set (0.00 sec) mysql> \! cat /data/auto.cnf [auto] server-uuid=a126fcb6-3706-11e8-b1d5-000c294ebf0d ``` > 说明: 事务在 GTID 中不可能具有序列号0。 >示例: "gtid_executed 5ad9cb8e-2092-11e7-ac95-000c29bf823d:1-6": 表示该server_uuid上执行了从1到6的事务。 使用 GTID 时,可以识别和跟踪每个事务,因为它在原始服务器上提交并被任何从实例应用;这意味着在启动新的从实例或故障转移到新主数据库时,无需使用 GTID 来引用日志文件或这些文件中的位置,这大大简化了这些任务。 因为基于GTID的复制完全是基于事务的,所以很容易判断主数据库和从实例是否一致;只要在主数据库上提交的所有事务也在从实例上提交,就可以保证两者之间的一致性。您可以将基于语句或基于行的复制与 GTID 一起使用; 但是,为了获得最佳结果,我们**建议您使用基于行的格式**。 GTID 始终保留在主数据库和从实例之间。这意味着您始终可以通过检查其二进制日志来确定应用于任何从实例的任何事务的来主数据库。此外,一旦在给定服务器上提交具有给定 GTID 的事务,该服务器将忽略具有相同 GTID 的任何后续事务。因此,在主数据库上提交的事务只能在从实例上应用一次,这有助于保证一致性。 ### GTID的优势 1. 更简单的实现failover,不用以前那样在需要找log_file和log_pos; 1. 更简单的搭建主从复制; 1. 比传统的复制更加安全; 1. GTID是连续的没有空洞的,保证数据的一致性,零丢失。 ## 2、GTID 的原理 **全局事务标识符 (GTID) 是在主数据库服务器(主数据库)上创建并与提交的每个事务相关联的唯一标识符。此标识符不仅对于它起主数据库的服务器是唯一的,而且对于给定复制拓扑中的所有服务器也是唯一的。** GTID 分配区分在主数据库上提交的客户端事务和在从实例上复制的复制事务。当客户端事务在主数据库上提交时,它会被分配一个新的 GTID,前提是该事务已写入二进制日志。客户端事务保证具有单调增加的 GTID,生成的数字之间没有间隙。 >如果客户端事务未写入二进制日志(例如,因为事务被过滤掉,或者事务是只读的),则不会在主数据库服务器上为其分配 GTID。 复制的事务保留与分配给主数据库服务器上的事务相同的 GTID。GTID 在复制事务开始执行之前就存在,即使复制事务没有写入从实例上的二进制日志,或者在从实例上被过滤掉,GTID 也会持久化。MySQL 系统表`mysql.gtid_executed`用于保存在 MySQL 服务器上应用的所有事务的分配 GTID,但存储在当前活动的二进制日志文件中的那些事务除外。 GTID 的自动跳过功能意味着在主数据库上提交的事务只能在从实例上应用一次,这有助于保证一致性。一旦在给定服务器上提交了具有给定 GTID 的事务,该服务器将忽略执行具有相同 GTID 的后续事务的任何尝试。不会引发错误,并且不会执行事务中的任何语句。 如果具有给定 GTID 的事务已开始在服务器上执行,但尚未提交或回滚,则任何尝试在具有相同 GTID 块的服务器上启动并发事务。服务器既不开始执行并发事务,也不将控制权返回给客户端。一旦事务的第一次尝试提交或回滚,在同一 GTID 上阻塞的并发会话可能会继续进行。如果第一次尝试回滚,一个并发会话将继续尝试事务,并且在同一 GTID 上阻塞的任何其他并发会话保持阻塞状态。如果第一次尝试提交,所有并发会话将停止被阻塞,并自动跳过事务的所有语句。 ### 2.1、gtid_executed表 GTID 存储在数据库中名为`gtid_executed`,的表中。该表中的完整一行数据包含了它所代表的每个GTID 或一组 GTID。 由 **原始服务器的UUID**、该组的**开始事务ID**和**结束事务ID**组成;对于仅引用单个 GTID 的行,这最后两个值是相同的。 ![](/media/202404/2024-04-25_134836_4316890.8606699399737364.png) 该`mysql.gtid_executed`表是在安装或升级 MySQL 服务器时创建的(如果它不存在),使用CREATE TABLE 类似于此处显示的语句: ```SQL CREATE TABLE gtid_executed ( source_uuid CHAR(36) 非空, interval_start BIGINT(20) 非空, interval_end BIGINT(20) 非空, PRIMARY KEY (source_uuid, interval_start) ) ``` >⚠️警告 与其他 MySQL 系统表一样,不要尝试自己创建或修改此表。 该`mysql.gtid_executed`表供 MySQL 服务器内部使用。 GTIDs存储在mysql.gtid_executed 表中。存储 GTID 的关键点取决于二进制日志记录是启用还是禁用: 只有当`gtid_mode`是 `ON` 或 `ON_PERMISSIVE`时才会启用 如果服务器意外停止,当前二进制日志文件中的 GTID 集不会保存在 mysql.gtid_executed表中。这些 GTID 在恢复期间从二进制日志文件添加到表中。例外情况是在服务器重新启动时未启用二进制日志记录。在这种情况下,服务器无法访问二进制日志文件来恢复 GTID,因此无法启动复制。 启用二进制日志记录时,该 mysql.gtid_executed表不会保存所有已执行事务的 GTID 的完整记录。该信息由gtid_executed系统变量的全局值提供 。始终使用 @@GLOBAL.gtid_executed在每次提交后更新的 来表示 MySQL 服务器的 GTID 状态,并且不查询 mysql.gtid_executed表。 ### 2.2、压缩gtid_executed表 随着时间的推移,该 mysql.gtid_executed表可能会充满许多行,这些行引用了主数据库自同一服务器的各个 GTID,并且其事务 ID 构成一个范围。 为了节省空间,MySQL 服务器mysql.gtid_executed通过用跨越整个事务标识符间隔的单行替换每组这样的行来定期压缩 表,如下所示: **压缩前示例** ```sql +--------------------------------------+----------------+--------------+ | source_uuid | interval_start | interval_end | +--------------------------------------+----------------+--------------+ | 66eafb32-a34f-11eb-af0a-020017006b39 | 1 | 23 | | 66eafb32-a34f-11eb-af0a-020017006b39 | 23 | 54 | | 66eafb32-a34f-11eb-af0a-020017006b39 | 54 | 77 | +--------------------------------------+----------------+--------------+ ``` **压缩后示例** ```sql +--------------------------------------+----------------+--------------+ | source_uuid | interval_start | interval_end | +--------------------------------------+----------------+--------------+ | 66eafb32-a34f-11eb-af0a-020017006b39 | 1 | 77 | +--------------------------------------+----------------+--------------+ ``` 您可以通过设置`gtid_executed_compression_period` 系统变量来控制在压缩表之前允许经过的事务数,从而控制压缩率。 此变量的默认值为 1000,这意味着默认情况下,每 1000 个事务后执行表压缩。 设置 `gtid_executed_compression_period` 为 0 将根本无法执行压缩,如果执行此操作,您应该准备好应对gtid_executed表可能需要大量增加的磁盘空间的潜在风险 。 mysql.gtid_executed表的压缩由名为`thread/sql/compress_gtid_table`的独立线程执行。 此线程未在`SHOW PROCESSLIST`下显示,但可以将其视为threads表中的一行 ,如下所示: ``` mysql> SELECT * FROM performance_schema.threads WHERE NAME LIKE '%gtid%'\G *************************** 1. row *************************** THREAD_ID: 23 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: 53428 PROCESSLIST_STATE: Suspending PROCESSLIST_INFO: NULL PARENT_THREAD_ID: 1 ROLE: NULL INSTRUMENTED: YES HISTORY: YES CONNECTION_TYPE: NULL THREAD_OS_ID: 1612 1 row in set (0.01 sec) mysql> ``` ## 3、GTID 生命周期 GTID 的生命周期包括以下步骤: 1. 在主实例上执行并提交事务。事务会被分配一个GTID。GTID 被写入主数据库的二进制日志(在日志中紧跟在事务本身之前)。如果事务未写入二进制日志(例如,因为事务被过滤掉,或者事务是只读的),则不会为其分配 GTID。 2. 如果为事务分配了 GTID,则 GTID 通过在事务开始时将其写入二进制日志(作为Gtid_log_event)在提交时以原子方式持久化 。每当二进制日志轮换或服务器关闭时,服务器都会将写入前一个二进制日志文件的所有事务的 GTID 写入 mysql.gtid_executed表中。 3. 如果为事务分配了 GTID,则通过将 GTID 添加到gtid_executed系统变量 ( `@@GLOBAL.gtid_executed`) 中的一组 GTID 中,该 GTID 将非原子地(在事务提交后不久)外化。此 GTID 集包含所有已提交 GTID 事务集的表示,它在复制中用作表示服务器状态的令牌。启用二进制日志记录(根据主数据库的要求),gtid_executed系统变量中的 GTID 集 是所应用事务的完整记录,但 mysql.gtid_executed表不是,因为最近的历史记录仍在当前的二进制日志文件中。 4. 在将二进制日志数据传输到从实例并存储在从实例的中继日志中后,从实例读取 GTID 并设置其gtid_next系统的值 变量作为此 GTID。这告诉从实例必须使用此 GTID 记录下一个事务。重要的是要注意从实例设置gtid_next在会话上下文中。 5. 从实例验证没有线程尚未获得 GTID 的所有权以处理gtid_next事务。通过首先读取和检查复制事务的 GTID,在处理事务本身之前,从实例不仅保证没有具有此 GTID 的先前事务已应用于从实例,而且还没有其他会话已读取此 GTID 但尚未提交相关事务。因此,如果多个客户端尝试同时应用同一个事务,服务器会通过只让其中一个执行来解决这个问题。从实例通过该gtid_owned 系统变量(`@@GLOBAL.gtid_owned`) 显示当前正在使用的每个 GTID 以及拥有它的线程的 ID。如果 GTID 已经被使用,则不会引发错误,并且使用自动跳过功能来忽略事务。 6. 如果尚未使用 GTID,则从实例将应用复制的事务。由于 gtid_next设置为主数据库已经分配的 GTID,从实例不会尝试为该事务生成新的 GTID,而是直接使用gtid_next. 7. 如果在从实例上启用了二进制日志记录,则 GTID 在提交时通过在事务开始时将其写入二进制日志(作为Gtid_log_event)以原子方式持久化 。每当二进制日志轮换或服务器关闭时,服务器都会将写入前一个二进制日志文件的所有事务的 GTID 写入 mysql.gtid_executed表中。 8. 如果在从实例上禁用了二进制日志记录,则 GTID 会通过将其直接写入mysql.gtid_executed表以原子方式持久化 。MySQL 在事务中附加一条语句以将 GTID 插入表中。在这种情况下,该 mysql.gtid_executed表是应用在从实例上的事务的完整记录。 >⚠️请注意: 1、 在 MySQL 5.7 中,将 GTID 插入表的操作对于 DML 语句是原子的,但对于 DDL 语句不是原子的,因此如果在涉及 DDL 语句的事务后服务器意外退出,GTID 状态可能会变得不一致。 2、 从 MySQL 8.0 开始,DDL 语句和 DML 语句的操作都是原子的。 9. 在从实例上提交复制事务后不久,GTID 通过将其添加到从实例的gtid_executed系统变量 ( `@@GLOBAL.gtid_executed`) 中的一组 GTID 以非原子方式外部化 。至于主数据库,这个 GTID 集包含所有提交的 GTID 事务集的表示。如果在从实例上禁用了二进制日志记录,则该 mysql.gtid_executed表也是应用在从实例上的事务的完整记录。如果在从实例上启用了二进制日志记录,这意味着某些 GTID 仅记录在二进制日志中,则gtid_executed系统变量中的 GTID 集是唯一完整的记录。 10. 在主数据库上完全过滤掉的客户端事务未分配 GTID,因此它们不会添加到gtid_executed系统变量中的事务集 或添加到mysql.gtid_executed表中。但是,在从实例上完全过滤掉的复制事务的 GTID 会保留下来。如果在从实例上启用了二进制日志记录,则过滤掉的事务将作为 aGtid_log_event后跟一个仅包含BEGINand COMMIT语句的空事务写入二进制日志。如果禁用二进制日志记录,则将过滤掉的事务的 GTID 写入mysql.gtid_executed表中。为过滤掉的事务保留 GTID 可确保 mysql.gtid_executedtable 和gtid_executed系统变量中的 GTID 集可以压缩。它还确保在从实例重新连接到主数据库时不会再次检索过滤掉的事务。 11. 在多线程从实例(带有`slave_parallel_workers > 0`)上,事务可以并行应用,因此复制的事务可以无序提交(除非已设置 `slave_preserve_commit_order=1`)。发生这种情况时,gtid_executed系统变量中的 GTID集 、包含多个 GTID 范围,它们之间存在间隙。(在主数据库或单线程从实例上,有单调增加的 GTID,数字之间没有间隙。)多线程从实例上的间隙仅出现在最近应用的事务中,并且随着复制的进行而填充。当复制线程使用STOP SLAVE语句,应用正在进行的事务以填补间隙。在关闭(例如服务器故障)或使用 KILL语句停止复制线程的情况下,间隙可能仍然存在。 ## 4、单主从GTID配置 ### 4.1、主从配置文件修改 配置环境: | 主机IP | OS版本 | MySQL版本 | 角色(master/slave) | 数据状态 | |----------------|----------|--------------|------------------|------| | 192.168.88.175 | centos 7 | MySQL 5.7.36 | master_gtid | 全新实例 | | 192.168.88.176 | centos 7 | MySQL 5.7.36 | slave1_gtid | 全新实例 | 主备MySQL配置文件配置说明:(主备如下配置均相同,除`server-id`必须保持不相同) ```bash [mysqld] log-bin=/data/mysql/binlog/mysql-bin # 必须项 sync-binlog=1 # 建议项 binlog_format=row # 建议项 server-id=100 # 必须项, 该ID配置必须保持全局唯一 enforce_gtid_consistency=on # gtid复制强唯一性 gtid_mode=on # gtid复制开启 ``` 重启所有实例使其生效 在master上创建一个用于复制的用户repl ### 4.2、创建复制用户 仅主实例配置 ```sql mysql> grant replication slave on *.* to repl@'192.168.88.%' identified by 'P@ssword1!'; ``` ### 4.3、配置同步信息 仅从实例配置 ``` mysql> change master to master_host='192.168.88.175', master_port=3306, master_auto_position=1; # gtid复制必须设置此项,使其自动定位position ``` 在MySQL 5.7版本,无需在`change master to`语句中加入user和password项, 而是在start slave语句中使用,否则会警告。 ### 4.4、启动复制线程 启动从实例复制线程 ```sql mysql> start slave user='repl' password='P@ssword1!'; ``` 检查复制状态 ```SQL mysql> show slave status\G; ``` Slave_IO_Running 和 Slave_SQL_Running 均Yes表示正常 ![](/media/202404/2024-04-25_145418_8365940.8368346416649658.png) ### 4.5、验证GTID复制 在master上插入测试数据并检查同步情况 使用存储过程生成测试数据 ```SQL -- 创建innodb类型的数值辅助表和插入数据的存储过程 CREATE TABLE num_innodb (n INT NOT NULL PRIMARY KEY) ENGINE = INNODB ; DROP PROCEDURE IF EXISTS proc_gen_nums; DELIMITER $$ CREATE PROCEDURE proc_gen_nums (num INT) BEGIN DECLARE rn INT DEFAULT 1 ; TRUNCATE TABLE num_innodb ; INSERT INTO num_innodb VALUES(1) ; dd: WHILE rn * 2 < num DO BEGIN INSERT INTO num_innodb SELECT rn + n FROM num_innodb; SET rn = rn * 2 ; END ; END WHILE dd; INSERT INTO num_innodb SELECT n + rn FROM num_innodb WHERE n + rn <= num ; END ; $$ DELIMITER ; ``` 生成模拟数据 ```SQL create database gtidb; use gtidb; call proc_gen_nums(10000); ``` 在slave上查看slave的状态,以下是同步结束后的状态信息。 ![](/media/202404/2024-04-25_150008_8130270.9072644763404625.png) 数据同步完成,复制正常 ## 5、对现有集群新增从实例 GTID复制是基于事务ID的,确切地说是binlog中的GTID,所以事务ID对GTID复制来说是命脉。 当master没有删除过任何binlog时,可以随意地向复制结构中添加新的slave,因为slave会复制所有的binlog到自己relay log中并replay。这样的操作尽管可能速度不佳,但胜在操作极其简便。 当master删除过一部分binlog后,在向复制结构中添加新的slave时,必须先获取到master binlog中当前已记录的第一个gtid之前的所有数据,然后恢复到slave上。只有slave上具有了这部分基准数据,才能保证和master的数据一致性。 而在实际环境中,往往会定期删除一部分binlog。所以,为了配置更通用的gtid复制环境,这里把前文的master的binlog给purge掉一部分。 目前master上的binlog使用情况如下,不难发现绝大多数操作都集中在master-bin.000004这个binlog中。 ### 5.1、模拟log日志清理 当前测试环境 | 主机IP | OS版本 | MySQL版本 | 角色(master/slave) | 数据状态 | |----------------|----------|--------------|------------------|------| | 192.168.88.175 | centos 7 | MySQL 5.7.36 | master_gtid | 已purge过binlog | | 192.168.88.176 | centos 7 | MySQL 5.7.36 | slave1_gtid | 已同步 | | 192.168.88.179 | centos 7 | MySQL 5.7.36 | slave2_gtid | 全新实例 | 刷新到新的binlog ```sql flush logs; ``` 指定新的log文件 ```sql purge master logs to 'mysql-bin.000003'; ``` 其中slave2的配置文件和slave1的配置文件完全相同: ```bash [mysqld] log-bin=/data/mysql/binlog/mysql-bin # 必须项 sync-binlog=1 # 建议项 binlog_format=row # 建议项 server-id=100 # 必须项, 该ID配置必须保持全局唯一 enforce_gtid_consistency=on # gtid复制强唯一性 gtid_mode=on # gtid复制开启 ``` ### 5.2 备份全量数据 采用mysqldump和冷备份的方式,因为gtid复制的特性,这些备份方式会很安全。 主实例上执行,备份所有数据:(示例命令未配置密码) ```bash mysqldump -uroot -A > all.sql ``` 从实例上执行,恢复所有数据:(示例命令未配置密码) ```bash mysql -uroot -e 'RESET MASTER;' mysql -u root < all.sql ``` ### 5.3 设置开启复制功能 从实例上执行 ``` change master to master_host='192.168.88.175',master_port=3306,master_auto_position=1; ``` 查看从实例的状态,看是否正确启动了复制功能。 ![](/media/202404/2024-04-25_153606_3093420.2305302585788659.png) ## 6、一张图说明GTID复制 ![](/media/202404/2024-04-25_153634_0574130.18070982893683396.png)
Nathan
April 25, 2024, 3:36 p.m.
转发文档
Collection documents
Last
Next
手机扫码
Copy link
手机扫一扫转发分享
Copy link
Markdown文件
PDF文件
Docx文件
share
link
type
password
Update password