14、MySQL基于GTID的数据同步

基于binlog位点主从复制痛点分析

痛点 1:首次开启主从复制的步骤复杂

  • 第一次开启主从同步时,要求从库和主库是一致的。
  • 找到主库的 binlog 位点。
  • 设置从库的 binlog 位点。
  • 开启从库的复制线程。
    痛点 2: 恢复主从复制的步骤复杂
  • 找到从库复制线程停止时的位点。
  • 解决复制异常的事务。无法解决时就需要手动跳过指定类型的错误,比如通过设置 slave_skip_errors=1032,1062。当然这个前提条件是跳过这类错误是无损的。(1062 错误是插入数据时唯一键冲突;1032 错误是删除数据时找不到行)
    不论是首次开启同步时需要找位点和设置位点,还是恢复主从复制时,设置位点和忽略错误, 这些步骤都显得过于复杂,而且容易出错 。 所以 MySQL 5.6 版本引入了 GTID,彻底解决了这个困难。

基于全局事务标识符(GTID)复制

官网: https://dev.mysql.com/doc/refman/8.0/en/replication-gtids.html
GTID是一个基于原始mysql服务器 生成的一个已经被成功执行的全局事务ID,它由服务器ID以及事务ID组合而成。这个全局事务ID不仅仅在原始服务器器上唯一,在所有存在主从关系 的mysql服务器上也是唯一的。正是因为这样一个特性使得mysql的主从复制变得更加简单,以及数据库一致性更可靠。

  • 一个GTID在一个服务器上只执行一次,避免重复执行导致数据混乱或者主从不一致。
  • GTID用来代替传统复制方法,不再使用MASTER_LOG_FILE+MASTER_LOG_POS开启复制。而是使用MASTER_AUTO_POSTION=1的方式开始复制。
  • 在传统的replica端,binlog是不用开启的,但是在GTID中replica端的binlog是必须开启的,目的是记录执行过的GTID(强制)。

GTID 的优势

  • 更简单的实现 failover,不用以前那样在需要找位点(log_file 和 log_pos)。
  • 更简单的搭建主从复制。
  • 比传统的复制更加安全。
  • GTID 是连续的没有空洞的,保证数据的一致性,零丢失。

GTID结构

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

复制代码
GTID = source_id:transaction_id
  • source_id标识source服务器,即源服务器唯一的server_uuid,由于GTID会传递到replica,所以也可以理解为源ID。

  • transaction_id是一个序列号,由事务在源上提交的顺序决定。序列号的上限是有符号64位整数(2^63-1)
    例如,最初要在UUID为3E11FA47-71CA-11E1-9E33-C80AA9429562的服务器上提交的第23个事务具有此GTID

    3E11FA47-71CA-11E1-9E33-C80AA9429562:23

GTID集合是由一个或多个GTID或GTID范围组成的集合。来自同一服务器的一系列gtid可以折叠成单个表达式,如下所示:

复制代码
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5

源自同一服务器的多个单一gtid或gtid范围也可以包含在单个表达式中,gtid或范围以冒号分隔,如下例所示:

复制代码
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-3:11:47-49

GTID集合可以包括单个GTID和GTID范围的任意组合,也可以包括来自不同服务器的GTID。

复制代码
2174B383-5441-11E8-B90A-C80AA9429562:1-3, 24DA167-0C0C-11E8-8442-00059A3C7B00:1-19

GTID存储在mysql数据库中名为gtid_executed的表中。该表中的一行包含它所代表的每个GTID或GTID集合的起始服务器的UUID,以及该集合的开始和结束事务id。

GTID工作原理

主库计算主库 GTID 集合和从库 GTID 的集合的差集,主库推送差集 binlog 给从库。
当从库设置完同步参数后,主库 A 的 GTID 集合记为集合 x,从库 B 的 GTID 集合记为 y。从库同步的逻辑如下:

  • 从库 B 指定主库 A,基于主备协议建立连接。
  • 从库 B 把集合 y 发给主库 A。
  • 主库 A 计算出集合 x 和集合 y 的差集,也就是集合 x 中存在,集合 y 中不存在的 GTID 集合。比如集合 x 是 1~100,集合 y 是 1~90,那么这个差集就是 91~100。这里会判断集合 x 是不是包含有集合 y 的所有 GTID,如果不是则说明主库 A 删除了从库 B 需要的 binlog,主库 A 直接返回错误。
  • 主库 A 从自己的 binlog 文件里面,找到第一个不在集合 y 中的事务GTID,也就是找到了 91。
  • 主库 A 从 GTID = 91 的事务开始,往后读 binlog 文件,按顺序取 binlog,然后发给 B。从库 B 的 I/O 线程读取 binlog 文件生成 relay log,SQL 线程解析 relay log,然后执行 SQL 语句。

GTID 同步方案和位点同步的方案区别是:

  • 位点同步方案是通过人工在从库上指定哪个位点,主库就发哪个位点,不做日志的完整性判断。
  • 而 GTID 方案是通过主库来自动计算位点的,不需要人工去设置位点,对运维人员友好。

GTID的配置

1) 修改主库配置
修改主库的配置文件

复制代码
#GTID:
#启用全局事务标识符(GTID)模式    
gtid_mode=on    
# 强制GTID的一致性。这意味着在执行事务时,MySQL将确保所有涉及的服务器都使用相同的GTID集。
enforce_gtid_consistency=on  

2)修改从库配置

修改从库配置文件

复制代码
#GTID:
gtid_mode=on  
enforce_gtid_consistency=on

从节点设置主库信息

复制代码
# 从库配置同步参数
mysql> CHANGE MASTER TO
     >     MASTER_HOST = host,
     >     MASTER_PORT = port,
     >     MASTER_USER = user,
     >     MASTER_PASSWORD = password,
     >     MASTER_AUTO_POSITION = 1;  

Or from MySQL 8.0.23:
mysql> CHANGE REPLICATION SOURCE TO
     >     SOURCE_HOST = host,
     >     SOURCE_PORT = port,
     >     SOURCE_USER = user,
     >     SOURCE_PASSWORD = password,
     >     SOURCE_AUTO_POSITION = 1;

SOURCE_AUTO_POSITION = 1: 这告诉从服务器使用自动位置跟踪功能,以便它可以自动从主服务器获取最新的二进制日志事件,而无需手动指定位置。

基于GTID主从复制示例

文档:https://dev.mysql.com/doc/refman/8.0/en/replication-gtids-howto.html

在前面基于binlog日志主从复制的mysql服务上配置GTID复制。

  1. 同步所有的mysql服务器

在主从服务器上都执行下面的命令:

复制代码
# 设置MySQL服务器的全局只读模式
mysql> SET @@GLOBAL.read_only = ON;

注意:只有在使用已经在进行复制而不使用gtid的服务器时才需要此步骤。对于新服务器,请继续执行步骤3。

  1. 停止所有的服务器

    docker stop mysql-source mysql-replica1 mysql-replica2

  2. 主从节点都启用GTID

修改custom.cnf

复制代码
# 启用GTID
gtid_mode=ON
enforce-gtid-consistency=ON

启动主从节点

复制代码
docker start mysql-source mysql-replica1 mysql-replica2

4)从节点配置基于GTID的自动定位

进入从节点,执行下面命令:

复制代码
mysql> stop replica;
mysql> change replication source to source_host='192.168.65.185', source_user='fox', source_password='123456', source_port=3307,source_auto_position=1;
  1. 开启从库复制,并禁用只读模式

    开启从库复制

    mysql> start replica;

    只有在步骤1中将服务器配置为只读时,才需要执行以下步骤。要允许服务器再次开始接受更新

    mysql> SET @@GLOBAL.read_only = OFF;

查看从库状态是否正常

复制代码
mysql> show replica status \G
主从切换演练

场景1: 模拟主库down机、从库1数据同步完成、从库2数据未同步完成

1)从库2停止复制

复制代码
mysql> stop replica;

2)主库创建测试数据

复制代码
INSERT INTO `test`.`user` VALUES (12, 'fox', NULL, NULL, NULL);
  1. 查询数据

从库1

从库2

很显然,从库1同步了最新数据,比从库2数据新

场景2:将主库宕机,从库1升级为主库、从库2切换主库为从库1(新的主库),观察从库2是否同步未完成的事务

1)停止主库

复制代码
docker stop mysql-source

2)设置新主库

设置replica1 为replica2的主库,因为replica1的数据是完整的。

按照普通复制方法,需要计算主库的log_pos和从库设置成主库的log_pos,可能出现错误

因为同一事务的GTID在所有节点上的值一致,那么根据replica2当前停止点的GTID就能定位到要主库的GTID,所以直接在replica2上执行change即可

复制代码
# replica1上创建复制用户
CREATE USER 'fox'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
GRANT REPLICATION SLAVE ON *.* TO 'fox'@'%';
flush privileges;

# replica2上执行 从replica1查询
mysql> stop replica;
mysql> change replication source to source_host='192.168.65.185',source_port=3308,source_user='fox',source_password='123456',source_auto_position=1;
 mysql> start replica; 

查询同步结果

场景3:模拟从库删除测试表,主库对表进行插入操作。观察从库复制是否报错

1)replica1删除test.user表,主库插入新记录

复制代码
# 从库1 删除user表
drop table test.user;
# 主库插入新记录
INSERT INTO `test`.`user` VALUES (14, 'AAA', NULL, NULL, NULL);
  1. 查看从库同步情况

    从库1执行

    mysql> show replica status\G

报错信息:事务aac92b21-b6a4-11ee-bab5-0242ac120002:6执行失败

Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 1 failed executing transaction 'aac92b21-b6a4-11ee-bab5-0242ac120002:6' at master log mysql-bin.000008, end_log_pos 1335. See error log and/or performance_schema.replication_applier_status_by_worker table for more details about this failure or others, if any.

3)在主库继续进行其他事务,观察gitd是否复制成功

复制代码
# 主库插入新记录
INSERT INTO `test`.`user` VALUES (15, 'BBB', NULL, NULL, NULL);

从库状态

可以看出从库复制中断 (注意:删除了表,无法插入)

4)复制中断修复:采用从库跳过错误事务修复

因为从库user表已经删了(user表中部分数据不是利用gtid复制过去的),先从主库将表数据拷贝到从库

获取从库最新状态

从库执行

复制代码
# 1.停止从库1复制进程
mysql> stop replica;
# 2.设置事务号,事务号从 Retrieved_Gtid_Set 获取,在session里设置gtid_next,即跳过这个GTID
# 注意,选择跳过出现错误的事务
mysql> SET @@SESSION.GTID_NEXT= 'aac92b21-b6a4-11ee-bab5-0242ac120002:7';
# 3.设置空事物
mysql> BEGIN; COMMIT;
# 4.恢复自增事物号
mysql> SET SESSION GTID_NEXT = AUTOMATIC;
# 5.启动从库1复制进程
mysql> start replica;
# 再次查询会发现主库数据已经同步过来了
mysql> select * from test.user;

如果需要一次跳过多条,找出需要跳过的gtid,批量执行

复制代码
stop replica; 
SET @@SESSION.GTID_NEXT= 'd58e6bad-ef3e-11ee-8285-0242ac120003:4';begin;commit;
SET @@SESSION.GTID_NEXT= 'd58e6bad-ef3e-11ee-8285-0242ac120003:5';begin;commit;
SET @@SESSION.GTID_NEXT= 'd58e6bad-ef3e-11ee-8285-0242ac120003:6';begin;commit;
SET SESSION GTID_NEXT = AUTOMATIC;
start replica;

完整的演示步骤:

1.从库删除user表

复制代码
mysql> drop table user;

2.主库连续删除两条数据

复制代码
mysql> select * from user;
+----+--------+-----------------+--------------+------------+
| id | name   | address         | last_updated | is_deleted |
+----+--------+-----------------+--------------+------------+
|  2 | 张三   | 广州白云山      |   1691563465 |          0 |
|  3 | fox    | NULL            |         NULL |       NULL |
|  4 | 李四   | NULL            |         NULL |       NULL |
|  5 | 111    | NULL            |         NULL |       NULL |
| 12 | fox    | NULL            |         NULL |       NULL |
| 14 | AAA    | NULL            |         NULL |       NULL |
| 15 | BBB    | NULL            |         NULL |       NULL |
| 16 | CCC    | NULL            |         NULL |       NULL |
| 20 | DDD    | NULL            |         NULL |       NULL |
| 22 | EEE    | NULL            |         NULL |       NULL |
+----+--------+-----------------+--------------+------------+
10 rows in set (0.00 sec)

mysql> delete from user where id=20;
Query OK, 1 row affected (0.19 sec)

mysql> select * from user;
+----+--------+-----------------+--------------+------------+
| id | name   | address         | last_updated | is_deleted |
+----+--------+-----------------+--------------+------------+
|  2 | 张三   | 广州白云山      |   1691563465 |          0 |
|  3 | fox    | NULL            |         NULL |       NULL |
|  4 | 李四   | NULL            |         NULL |       NULL |
|  5 | 111    | NULL            |         NULL |       NULL |
| 12 | fox    | NULL            |         NULL |       NULL |
| 14 | AAA    | NULL            |         NULL |       NULL |
| 15 | BBB    | NULL            |         NULL |       NULL |
| 16 | CCC    | NULL            |         NULL |       NULL |
| 22 | EEE    | NULL            |         NULL |       NULL |
| 23 | 333    | NULL            |         NULL |       NULL |
+----+--------+-----------------+--------------+------------+
10 rows in set (0.00 sec)

mysql> delete from user where id=22;
Query OK, 1 row affected (0.29 sec)

mysql> select * from user;
+----+--------+-----------------+--------------+------------+
| id | name   | address         | last_updated | is_deleted |
+----+--------+-----------------+--------------+------------+
|  2 | 张三   | 广州白云山      |   1691563465 |          0 |
|  3 | fox    | NULL            |         NULL |       NULL |
|  4 | 李四   | NULL            |         NULL |       NULL |
|  5 | 111    | NULL            |         NULL |       NULL |
| 12 | fox    | NULL            |         NULL |       NULL |
| 14 | AAA    | NULL            |         NULL |       NULL |
| 15 | BBB    | NULL            |         NULL |       NULL |
| 16 | CCC    | NULL            |         NULL |       NULL |
| 23 | 333    | NULL            |         NULL |       NULL |
+----+--------+-----------------+--------------+------------+
9 rows in set (0.00 sec)
  1. 借助Navicat将主库user表复制到从库,包括数据。 这样一来从库就有了当前的全量数据,只需要跳过错误的gtid,从最新的gtid开始执行就可以了。

    查询从库,确定数据是否和主库一致

    mysql> select * from user;
    +----+--------+-----------------+--------------+------------+
    | id | name | address | last_updated | is_deleted |
    +----+--------+-----------------+--------------+------------+
    | 2 | 张三 | 广州白云山 | 1691563465 | 0 |
    | 3 | fox | NULL | NULL | NULL |
    | 4 | 李四 | NULL | NULL | NULL |
    | 5 | 111 | NULL | NULL | NULL |
    | 12 | fox | NULL | NULL | NULL |
    | 14 | AAA | NULL | NULL | NULL |
    | 15 | BBB | NULL | NULL | NULL |
    | 16 | CCC | NULL | NULL | NULL |
    | 23 | 333 | NULL | NULL | NULL |
    +----+--------+-----------------+--------------+------------+
    9 rows in set (0.00 sec)

  2. 获取从库需要配置跳过错误的事务

    获取到错误的gtid为d58e6bad-ef3e-11ee-8285-0242ac120003:12,

    因为连续删除过两条数据,所以d58e6bad-ef3e-11ee-8285-0242ac120003:13也是错误的事务

    mysql> show replica status\G;
    *************************** 1. row ***************************
    Replica_IO_State: Waiting for source to send event
    Source_Host: 192.168.65.223
    Source_User: fox
    Source_Port: 3308
    Connect_Retry: 30
    Source_Log_File: mysql-bin.000004
    Read_Source_Log_Pos: 4229
    Relay_Log_File: 3c946c781110-relay-bin.000012
    Relay_Log_Pos: 790
    Relay_Source_Log_File: mysql-bin.000004
    Replica_IO_Running: Yes
    Replica_SQL_Running: No
    Replicate_Do_DB:
    Replicate_Ignore_DB:
    Replicate_Do_Table:
    Replicate_Ignore_Table:
    Replicate_Wild_Do_Table:
    Replicate_Wild_Ignore_Table:
    Last_Errno: 1146
    Last_Error: Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 1 failed executing transaction 'd58e6bad-ef3e-11ee-8285-0242ac120003:12' at master log mysql-bin.000004, end_log_pos 3906. See error log and/or performance_schema.replication_applier_status_by_worker table for more details about this failure or others, if any.
    Skip_Counter: 0
    Exec_Source_Log_Pos: 3645
    Relay_Log_Space: 1932
    Until_Condition: None
    Until_Log_File:
    Until_Log_Pos: 0
    Source_SSL_Allowed: No
    Source_SSL_CA_File:
    Source_SSL_CA_Path:
    Source_SSL_Cert:
    Source_SSL_Cipher:
    Source_SSL_Key:
    Seconds_Behind_Source: NULL
    Source_SSL_Verify_Server_Cert: No
    Last_IO_Errno: 0
    Last_IO_Error:
    Last_SQL_Errno: 1146
    Last_SQL_Error: Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 1 failed executing transaction 'd58e6bad-ef3e-11ee-8285-0242ac120003:12' at master log mysql-bin.000004, end_log_pos 3906. See error log and/or performance_schema.replication_applier_status_by_worker table for more details about this failure or others, if any.
    Replicate_Ignore_Server_Ids:
    Source_Server_Id: 11
    Source_UUID: d58e6bad-ef3e-11ee-8285-0242ac120003
    Source_Info_File: mysql.slave_master_info
    SQL_Delay: 0
    SQL_Remaining_Delay: NULL
    Replica_SQL_Running_State:
    Source_Retry_Count: 86400
    Source_Bind:
    Last_IO_Error_Timestamp:
    Last_SQL_Error_Timestamp: 240401 14:17:40
    Source_SSL_Crl:
    Source_SSL_Crlpath:
    Retrieved_Gtid_Set: d58e6bad-ef3e-11ee-8285-0242ac120003:1-13,
    dfb1f706-ef3e-11ee-82cc-0242ac120004:2
    Executed_Gtid_Set: ce6baf4a-ef3e-11ee-99fb-0242ac120002:1-13,
    d58e6bad-ef3e-11ee-8285-0242ac120003:1-11,
    dfb1f706-ef3e-11ee-82cc-0242ac120004:1-2
    Auto_Position: 1
    Replicate_Rewrite_DB:
    Channel_Name:
    Source_TLS_Version:
    Source_public_key_path:
    Get_Source_public_key: 0
    Network_Namespace:
    1 row in set (0.00 sec)

  3. 在主库再次执行删除操作

    mysql> delete from user where id=12;
    Query OK, 1 row affected (0.11 sec)

    mysql> select * from user;
    +----+--------+-----------------+--------------+------------+
    | id | name | address | last_updated | is_deleted |
    +----+--------+-----------------+--------------+------------+
    | 2 | 张三 | 广州白云山 | 1691563465 | 0 |
    | 3 | fox | NULL | NULL | NULL |
    | 4 | 李四 | NULL | NULL | NULL |
    | 5 | 111 | NULL | NULL | NULL |
    | 14 | AAA | NULL | NULL | NULL |
    | 15 | BBB | NULL | NULL | NULL |
    | 16 | CCC | NULL | NULL | NULL |
    | 23 | 333 | NULL | NULL | NULL |
    +----+--------+-----------------+--------------+------------+

6.在从库

复制代码
mysql> select * from user;
+----+--------+-----------------+--------------+------------+
| id | name   | address         | last_updated | is_deleted |
+----+--------+-----------------+--------------+------------+
|  2 | 张三   | 广州白云山      |   1691563465 |          0 |
|  3 | fox    | NULL            |         NULL |       NULL |
|  4 | 李四   | NULL            |         NULL |       NULL |
|  5 | 111    | NULL            |         NULL |       NULL |
| 12 | fox    | NULL            |         NULL |       NULL |
| 14 | AAA    | NULL            |         NULL |       NULL |
| 15 | BBB    | NULL            |         NULL |       NULL |
| 16 | CCC    | NULL            |         NULL |       NULL |
| 23 | 333    | NULL            |         NULL |       NULL |
+----+--------+-----------------+--------------+------------+
9 rows in set (0.00 sec)

mysql> stop replica;
Query OK, 0 rows affected (0.07 sec)
# 如果需要一次跳过多条,找出需要跳过的gtid,批量执行
mysql> SET @@SESSION.GTID_NEXT= 'd58e6bad-ef3e-11ee-8285-0242ac120003:12';begin;commit;
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.12 sec)

mysql> SET @@SESSION.GTID_NEXT= 'd58e6bad-ef3e-11ee-8285-0242ac120003:13';begin;commit;
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql> SET SESSION GTID_NEXT = AUTOMATIC;
Query OK, 0 rows affected (0.00 sec)

mysql> start replica;
Query OK, 0 rows affected (1.10 sec)

mysql> select * from user;
+----+--------+-----------------+--------------+------------+
| id | name   | address         | last_updated | is_deleted |
+----+--------+-----------------+--------------+------------+
|  2 | 张三   | 广州白云山      |   1691563465 |          0 |
|  3 | fox    | NULL            |         NULL |       NULL |
|  4 | 李四   | NULL            |         NULL |       NULL |
|  5 | 111    | NULL            |         NULL |       NULL |
| 14 | AAA    | NULL            |         NULL |       NULL |
| 15 | BBB    | NULL            |         NULL |       NULL |
| 16 | CCC    | NULL            |         NULL |       NULL |
| 23 | 333    | NULL            |         NULL |       NULL |
+----+--------+-----------------+--------------+------------+
8 rows in set (0.01 sec)
相关推荐
zzb15807 小时前
RAG from Scratch-优化-query
java·数据库·人工智能·后端·spring·mybatis
一只鹿鹿鹿7 小时前
信息安全等级保护安全建设防护解决方案(总体资料)
运维·开发语言·数据库·面试·职场和发展
堕2747 小时前
MySQL数据库《基础篇--数据库索引(2)》
数据库·mysql
wei_shuo7 小时前
数据库优化器进化论:金仓如何用智能下推把查询时间从秒级打到毫秒级
数据库·kingbase·金仓
71-38 小时前
MySQL的安装和卸载组件
笔记·学习·mysql
雷工笔记8 小时前
Navicat Premium 17 软件安装记录
数据库
wenlonglanying8 小时前
Ubuntu 系统下安装 Nginx
数据库·nginx·ubuntu
数据库小组8 小时前
10 分钟搞定!Docker 一键部署 NineData 社区版
数据库·docker·容器·database·数据库管理工具·ninedata·迁移工具
爬山算法8 小时前
MongoDB(38)如何使用聚合进行投影?
数据库·mongodb
l1t8 小时前
Deep Seek总结的APSW 和 SQLite 的关系
数据库·sqlite