暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

实战 my2sql 闪回

背景

上回文章说到,如果主从切换,主从的 gtid 可能不一致,主库的 gtid 比从库要多,文章的结论是,这些数据可以补到从库,也可以回退,以修复 gtid 到一致。由于我最近在测试 orchestrator 高可用,他做主从切换后是不会自动处理旧主的,也就是旧主的 gtid 既不会补到从库,也不会回退,所以这部分就需要我人手处理啦。

补数据的方式修复旧主是有前提条件的,适合于旧主 mysqld 挂了后拉不起来但 Linux server 没有挂的场景,并且要嵌入到高可用切换逻辑里。

以下高可用逻辑的第二步

  • 旧主卸载 VIP
  • 从库拉取旧主的 binlog 到从库回放,补数据,拉齐 gtid (一般通过 ssh 免密,或开发一个 agent 来做拉取操作)
  • 新主挂载 VIP

Linux server 如果挂了,就无法补数据了,所以只能重做数据库或者通过回退老主多余的事务来达到 gtid 一致。

如果这些多余的事务含 DDL,那只能重做,DDL 语句无法闪回。

闪回工具

1. binlog2sql

回退老主多余的事务,以前最常用的工具就是 binlog2sql 了,他可以基于 binlog 生成反向 SQL,即 binlog 里这个事务原本是 insert 语句,binlog2sql 可以让他生成反向的 SQL 行为,改为 delete 语句。同理,原本 delete 语句,反向 SQL 是 insert 语句,update 语句的反向 SQL 则是 binlog 的前后镜像交换。

把闪回工具生成的反向操作 SQL,插入数据库,通过这个技巧,实现了事务的“回滚”。

使用 binlog2sql 这个工具的前提是,mysqld 服务器开启了 binlog,并且设置为 row 格式,还有一个重要的,我以前文中有提到过 binlog_row_image = full
,好消息是这些都是 MySQL5.7 的默认值。

log_bin = /database/mysql/log/binlog/3306/mysql-bin
binlog_format = row
binlog_row_image = full

2. my2sql

一般情况下,binlog2sql 已经够用了,但我们使用 binlog2sql 时还是遇到了两大痛点:

  • binlog2sql 基于 python 编写,拥有 python 工具的通病,慢!
  • binlog2sql 不支持离线解析 binlog。

所以后面我测试了基于 go 语言开发支持多线程、支持离线解析的 my2sql 这个工具。他们对比如下图:

my2sql 速度有多快呢?下面是 my2sql 官方做的性能测试:

对比项my2sqlbinlog2sql
1.1G binlog生成回滚SQL1分40秒65分钟
1.1G binlog生成原始SQL1分30秒50分钟
1.1G binlog生成表DML统计信息、以及事务统计信息40秒不支持

CentOS7下my2sql 的安装部署

打开 https://github.com/liuhr/my2sql/blob/master/releases/centOS_release_7.x/my2sql

下载和上传到服务就可以用了,部署简单是 go 语言工具的优点,当然对于初学者来说我补充以下命令。

mv my2sql /usr/bin
chmod +x /usr/bin/my2sql 

实战

之前我的文章《技术分享 | 无损半同步复制下,主从高可用切换后数据一致吗?》(如果不知道我现在在干什么,强烈建议点击文章看看),提到主从切换后,主库有可能多 gtid,我现在就有一套主从数据库,切换后,旧主 gtid 多了,需要拉平 gtid。

旧主

mysql> show master status\G
*************************** 1. row ***************************
             File: mysql-bin.000031
         Position: 194
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 7b27b5a9-22c8-11ed-87b1-000c29375703:1-76977
1 row in set (0.00 sec)

新主

mysql> show master status\G
*************************** 1. row ***************************
             File: mysql-bin.000009
         Position: 234
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 7b03e5df-22c8-11ed-8e66-000c290f5e73:1-422755,
7b27b5a9-22c8-11ed-87b1-000c29375703:1-76972
1 row in set (0.01 sec)

看标红部分,旧主的 gtid 事务多了这五条:

7b27b5a9-22c8-11ed-87b1-000c29375703:76973-76977
也就是
7b27b5a9-22c8-11ed-87b1-000c29375703:76973
7b27b5a9-22c8-11ed-87b1-000c29375703:76974
7b27b5a9-22c8-11ed-87b1-000c29375703:76975
7b27b5a9-22c8-11ed-87b1-000c29375703:76976
7b27b5a9-22c8-11ed-87b1-000c29375703:76977

使用 my2sql 生成反向 SQL,就可以把里面的事务抵消掉,实现事务"回退"。

其实我的这个需求,是应该基于 gtid 编号回退的,但 my2sql 现在还不支持这个功能,只支持基于 pos 位置点。如下图:

第一步,我需要知道我要回退的gtid事务在哪个binlog里

如图,其实上次发生了问题后,我没有动过这个主库,就是重启过几次,所以生成了一些 binlog 文件,这些 binlog 文件都是空的,也就是不含 gtid 的,所以我的 gtid 大概率是在 899M 大小的 mysql-bin.000027 里了。

如何确认?我们可以解析 mysql-bin.000027 文件看看是不是。当然我们也可以直接解析  mysql-bin.000028 看看。

mysqlbinlog -vvv mysql-bin.000028 |less

# The proper term is pseudo_replica_mode, but we use this compatibility alias
# to make the statement usable on server versions 8.0.24 and older.
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#220825 16:30:05 server id 18  end_log_pos 123 CRC32 0xd2db1719         Start: binlog v 4, server v 5.7.39-log created 220825 16:30:05 at startup
ROLLBACK/*!*/;
BINLOG '
DTMHYw8SAAAAdwAAAHsAAAAAAAQANS43LjM5LWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAANMwdjEzgNAAgAEgAEBAQEEgAAXwAEGggAAAAICAgCAAAACgoKKioAEjQA
ARkX29I=

'
/*!*/;
# at 123
#220825 16:30:08 server id 18  end_log_pos 194 CRC32 0x1fa1d288         Previous-GTIDs
7b27b5a9-22c8-11ed-87b1-000c29375703:1-76977
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

看标红部分,他的意思是这个 binlog 开始之前的 gtid 集合是 7b27b5a9-22c8-11ed-87b1-000c29375703:1-76977。

那么说明了 7b27b5a9-22c8-11ed-87b1-000c29375703:76973-76977 都在上一个 binlog 文件里啊,也就是我们猜对了,在 mysql-bin.000027 文件里。

当然了,如果你能连接数据库,不用解析 binlog,可以在数据库里查,如下图

我们使用 show binlog events
命令查询这两个连续的 binlog 的头部,能发现 mysql-bin.000027 之前的 gtid 集合为 7b27b5a9-22c8-11ed-87b1-000c29375703:1-10,而 mysql-bin.000028 之前的 gtid 集合为 7b27b5a9-22c8-11ed-87b1-000c29375703:1-76977,所以说明 mysql-bin.000027 包含的 gtid 集合为 7b27b5a9-22c8-11ed-87b1-000c29375703:11-76977。

所以,7b27b5a9-22c8-11ed-87b1-000c29375703:76973-76977 这五条 gtid 事务是在 mysql-bin.000027 文件里

第二步,在文件里基于第一个要回退的gtid定位到pos

Pos 位置为 942047161

第三步,在文件里基于最后一个要回退的gtid定位到 pos

我这里不涉及。我让 my2sql 直接读到文件结尾即可。

第四步,执行my2sql闪回生成SQL的命令

#伪装成从库解析binlog
my2sql  -user admin -password xxxx -host 127.0.0.1 -port 3307 \
 -mode repl \
 -work-type rollback \
 -start-file mysql-bin.000027 -start-pos 942047161 \
 -stop-file mysql-bin.000027 -stop-pos 942058425 \
 -output-dir ./tmpdir

#直接读取binlog文件解析
my2sql  -user admin -password xxxx -host 127.0.0.1 -port 3307 \
 -mode file -local-binlog-file /tmp/mysql-bin.000027 \
 -work-type rollback \
 -start-file /tmp/mysql-bin.000027  -start-pos 942047161 \
 -stop-file /tmp/mysql-bin.000027 -stop-pos 942058425 \
 -output-dir ./tmpdir

重要参数解析:

  • -mode
    • repl    伪装成从库的解析,是默认值,要求 mysqld 在线
    • file     文件模式,适合离线解析
  • -work-type
    • 2sql     生成原始sql
    • rollback  生成回滚sql
    • stats    只统计DML、事务信息

需要注意的坑或者bug

你可能觉得如果解析 binlog,如果从某个位置开始解析到 binlog 的结尾,那么不需要加 -stop-file 和 -stop-pos,我在上面的第三步也是这么认为的。

但是你想的是错的!

  1. 坑:-mode repl 模式下,如果不加 stop-file 和 -stop-pos,那么他会把一直解析到最新的一个 binlog file 为止。

我测试前偷偷写了一条 gtid 进去,导致 mysql-bin.000031 其实也是有事务的。我不加 stop-file 和 -stop-pos 他会一直解析,帮我把 mysql-bin.000031 的 SQL 也生成了一个反向 SQL (我谢谢你啊!)

没有bug!这是合理的!因为你是在线模式,你不给结束位置,一直解析到能读到的最后一个 binlog 的末尾,这很正常。但这是一个要注意的细节,不要用 mysqlbinlog 那种分析离线文件的思维。

  1. 坑或 bug:语法解析有坑,个人认为这个可以定为 bug

那说明如果我这里敲错了,会忽略后面所有参数。。乱敲能实现一样的效果。

我把这个行为做成更隐蔽,这次我 -stop-file 敲对了,但画蛇添足,前面多两个字母。

实际上,我觉得这个坑很危险。很容易出现犯错而不知道的情况下产生数据错乱。

  1. Bug: -mode file 模式下,已经可以限定 my2sql 只解析那个 binlog 文件了,如果想解析到结尾,是否可以省掉输入 stop-file 和 -stop-pos 呢,答案是:可以,但有个 bug!

相对路径,没有问题!

my2sql  -user admin -password xxxx -host 127.0.0.1 -port 3307 \
 -mode file -local-binlog-file mysql-bin.000027 \
 -work-type rollback \
 -start-file mysql-bin.000027  -start-pos 942047161 \
 -output-dir ./tmpdir

绝对路径,有bug!

my2sql  -user admin -password xxxx -host 127.0.0.1 -port 3307 \
 -mode file -local-binlog-file /database/mysql/log/binlog/3307/mysql-bin.000027 \
 -work-type rollback \
 -start-file /database/mysql/log/binlog/3307/mysql-bin.000027  -start-pos 942047161 \
 -output-dir ./tmpdir2

我们看下面的测试图

那我加 stop-file 和 -stop-pos 呢?也不行,这次是不生成反向 SQL 了。

所以这个 bug 和 stop-file、-stop-pos 无关了,是绝对路径玩法有 bug。

  1. 坑: stop-pos 位置要找对。找 stop-pos 是找最后一个 gtid commit 之后的 pos 点,而不是这个 binlog 的最后一个 pos。

如图,这个 binlog 文件的最后一个 pos 点是 942058425,但他是在 COMMIT 之前的。所以不能定这个为 -stop-pos,所以 -stop-pos 应该在在下一个 binlog 里的第一个 pos。

所以我之前写的

my2sql  -user admin -password xxxxx -host 127.0.0.1 -port 3307 \
 -mode repl \
 -work-type rollback \
 -start-file mysql-bin.000027  -start-pos 942047161 \
 -stop-file mysql-bin.000027 -stop-pos 942058425 \
 -output-dir ./tmpdir
应该改为
my2sql  -user admin -password xxxxx -host 127.0.0.1 -port 3307 \
 -mode repl \
 -work-type rollback \
 -start-file mysql-bin.000027  -start-pos 942047161 \
 -stop-file mysql-bin.000028 -stop-pos 4 \
 -output-dir ./tmpdir

导入反向SQL,"回滚"事务

我们继续做这个闪回实验

[root@mysql01 tmpdir]# cat rollback.27.sql |wc -l
22
[root@mysql01 tmpdir]# ls
biglong_trx.txt  binlog_status.txt  rollback.27.sql
[root@mysql01 tmpdir]# mysql -uadmin -pxxxxx -h127.0.0.1 -P3307 < rollback.27.sql 
mysql: [Warning] Using a password on the command line interface can be insecure.

mysql> show master status\G
*************************** 1. row ***************************
             File: mysql-bin.000031
         Position: 14967
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 7b27b5a9-22c8-11ed-87b1-000c29375703:1-76999
1 row in set (0.00 sec)

之前是有五条 gtid 的事务需要闪回,这五个事务实际包含了多条 SQL,生成的反向 SQL 有 22 条,这些反向 SQL,my2sql 是不会把他们包裹回事务的。所以我导入反向 SQL 时不只增加 5条 gtid,而是增加 22 条。

之前 gtid 是 7b27b5a9-22c8-11ed-87b1-000c29375703:1-76977,我们插入 22 条 SQL 后,增加到 7b27b5a9-22c8-11ed-87b1-000c29375703:1-76999,这没毛病。

修改gtid编号

现在我们的旧主和新主的数据实际上是一样的,但 gtid 编号还不一样。我们把旧主 gtid 修改到和 新主 gtid 一样就可以了。

mysql> reset master;
Query OK, 0 rows affected (0.06 sec)

mysql> set global gtid_purged="7b27b5a9-22c8-11ed-87b1-000c29375703:1-76972";
Query OK, 0 rows affected (0.00 sec)

恢复主从集群——建立新主到旧主的复制关系

mysql01> CHANGE MASTER TO
    -> MASTER_HOST='mysql02',
    -> MASTER_USER='repl'
    -> MASTER_PASSWORD='password',
    -> MASTER_PORT=3307,
    -> MASTER_AUTO_POSITION=1;
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql01> show master status\G
*************************** 1. row ***************************
             File: mysql-bin.000001
         Position: 154
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 7b27b5a9-22c8-11ed-87b1-000c29375703:1-76972
1 row in set (0.00 sec)

mysql01> start slave;
Query OK, 0 rows affected (0.02 sec)

mysql01> show master status\G
*************************** 1. row ***************************
             File: mysql-bin.000001
         Position: 159934
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 7b03e5df-22c8-11ed-8e66-000c290f5e73:1-70,
7b27b5a9-22c8-11ed-87b1-000c29375703:1-76972
1 row in set (0.01 sec)

mysql01> show master status\G
*************************** 1. row ***************************
             File: mysql-bin.000001
         Position: 1793898
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 7b03e5df-22c8-11ed-8e66-000c290f5e73:1-793,
7b27b5a9-22c8-11ed-87b1-000c29375703:1-76972
1 row in set (0.00 sec)

集群恢复了,复制延迟的 gtid 在追赶中。

总结

因为工作需要,使用了一下 my2sql,快捷方便,满足了我的使用需求。但我只是稍微测试一下,就发现工具的一些小坑小 bug,工具可能实际上的问题还挺多,产品还需要打磨和更多的测试,目前我仅用于测试环境,旧主库 gtid 不一致的问题,xtrabackup 备份重做可能仍然是生产上最佳的选择。


最后修改时间:2023-05-26 16:27:40
文章转载自芬达的数据库学习笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论