Mysql5.7 XA事物的改进

问题来源

在使用otter数据同步项目的时候,由于业务方使用了XA事物,预发环境又是mysql5.7版本,导致XA命令不支持,当改造完otter支持XA命令解析测试的时候,测试发现XA的Rollback数据居然也同步成功了,最终定位到原来mysql在5.7版本之后,增加了在XA Prepare之后就会将binlog生成,发生XA Rollback只是发送命令,无法得知需要更新的数据信息,这就导致到otter数据不一致问题。以下是整理的关于mysql 5.7关于XA的知识点。

什么是XA

XA(分布式事务)规范主要定义了(全局)事务管理器(TM: Transaction Manager)和(局部)资源管理器(RM: Resource Manager)之间的接口。XA为了实现分布式事务,将事务的提交分成了两个阶段:也就是2PC (tow phase commit),XA协议就是通过将事务的提交分为两个阶段来实现分布式事务。

两阶段
1)prepare 阶段
事务管理器向所有涉及到的数据库服务器发出prepare”准备提交”请求,数据库收到请求后执行数据修改和日志记录等处理,处理完成后只是把事务的状态改成”可以提交”,然后把结果返回给事务管理器。即:为prepare阶段,TM向RM发出prepare指令,RM进行操作,然后返回成功与否的信息给TM。
2)commit 阶段
事务管理器收到回应后进入第二阶段,如果在第一阶段内有任何一个数据库的操作发生了错误,或者事务管理器收不到某个数据库的回应,则认为事务失败,回撤所有数据库的事务。数据库服务器收不到第二阶段的确认提交请求,也会把”可以提交”的事务回撤。如果第一阶段中所有数据库都提交成功,那么事务管理器向数据库服务器发出”确认提交”请求,数据库服务器把事务的”可以提交”状态改为”提交完成”状态,然后返回应答。即:为事务提交或者回滚阶段,如果TM收到所有RM的成功消息,则TM向RM发出提交指令;不然则发出回滚指令。

外部与内部XA
MySQL中的XA实现分为:外部XA和内部XA。前者是指我们通常意义上的分布式事务实现;后者是指单台MySQL服务器中,Server层作为TM(事务协调者,通常由binlog模块担当),而服务器中的多个数据库实例作为RM,而进行的一种分布式事务,也就是MySQL跨库事务;也就是一个事务涉及到同一条MySQL服务器中的两个innodb数据库(目前似乎只有innodb支持XA)。内部XA也可以用来保证redo和binlog的一致性问题。

2.2. redo与binlog的一致性问题
我们MySQL为了兼容其它非事务引擎的复制,在server层面引入了 binlog, 它可以记录所有引擎中的修改操作,因而可以对所有的引擎使用复制功能; 然而这种情况会导致redo log与binlog的一致性问题;MySQL通过内部XA机制解决这种一致性的问题。
第一阶段:InnoDB prepare, write/sync redo log;binlog不作任何操作;
第二阶段:包含两步,1> write/sync Binlog; 2> InnoDB commit (commit in memory);
当然在5.6之后引入了组提交的概念,可以在IO性能上进行一些提升,但总体的执行顺序不会改变。
当第二阶段的第1步执行完成之后,binlog已经写入,MySQL会认为事务已经提交并持久化了(在这一步binlog就已经ready并且可以发送给订阅者了)。在这个时刻,就算数据库发生了崩溃,那么重启MySQL之后依然能正确恢复该事务。在这一步之前包含这一步任何操作的失败都会引起事务的rollback。
第二阶段的第2大部分都是内存操作,比如释放锁,释放mvcc相关的read view等等。MySQL认为这一步不会发生任何错误,一旦发生了错误那就是数据库的崩溃,MySQL自身无法处理。这个阶段没有任何导致事务rollback的逻辑。在程序运行层面,只有这一步完成之后,事务导致变更才能通过API或者客户端查询体现出来。

MySQL 5.7 XA可靠性改进

MySQL 5.7解决了 xa prepare了的事务的严格持久化问题,也就是在session断开和实例崩溃重启情况下这些事务不丢,同时在 xa prepare ‘xid1’返回之前XA事务也会同步到备库。下面将通过在5.6和5.7上分别执行xa prepare并对binlog event进行分析 来演示这个改进。

断开连接对xa prepare的事务影响

在5.6和5.7上分别执行如下sql然后断开连接,再重新连接使用的xa recover验证 XA 事务是否回滚了。

1
2
3
4
5
xa start 'xid1';
insert into test values(1, 1);
xa end 'xid1';
xa prepare 'xid1';
-- 这里断开再连上新连接执行 xa recover

在 5.6 的版本上将返回空的结果,在 5.7 的版本上返回:

1
2
3
4
5
6
7
mysql> xa recover;
+----------+--------------+--------------+------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+------+
| 1 | 4 | 0 | xid1 |
+----------+--------------+--------------+------+
1 row in set (0.00 sec)

说明断开连接后 5.7的prepare了的xa事务没有丢失。

XA 事务的 Binlog events 异同

在5.6和5.7上分别执行如下事务,然后用 show binlog events 查看两者binlog的不同:

1
2
3
4
5
xa start 'xid1';
insert into test values(1, 1);
xa end 'xid1';
xa prepare 'xid1';
xa commit 'xid1';

5.6的结果:

1
2
3
4
5
mysql-bin.000001 | 304 | Gtid           |      3706 |         352 | SET @@SESSION.GTID_NEXT= 'uuid:2'
mysql-bin.000001 | 352 | Query | 3706 | 424 | BEGIN
mysql-bin.000001 | 424 | Table_map | 3706 | 472 | table_id: 71 (test.test)
mysql-bin.000001 | 472 | Write_rows | 3706 | 516 | table_id: 71 flags: STMT_END_F
mysql-bin.000001 | 516 | Query | 3706 | 589 | COMMIT

5.7的结果:

1
2
3
4
5
6
7
8
mysql-bin.000001 |  544 | Gtid           |      3707 |         592 | SET @@SESSION.GTID_NEXT= 'uuid:3'
mysql-bin.000001 | 592 | Query | 3707 | 685 | XA START X'78696431',X'',1
mysql-bin.000001 | 685 | Table_map | 3707 | 730 | table_id: 74 (test.t)
mysql-bin.000001 | 730 | Write_rows | 3707 | 774 | table_id: 74 flags: STMT_END_F
mysql-bin.000001 | 774 | Query | 3707 | 865 | XA END X'78696431',X'',1
mysql-bin.000001 | 865 | XA_prepare | 3707 | 905 | XA PREPARE X'78696431',X'',1
mysql-bin.000001 | 905 | Gtid | 3707 | 953 | SET @@SESSION.GTID_NEXT= 'uuid:4' |
mysql-bin.000001 | 953 | Query | 3707 | 1047 | XA COMMIT X'78696431',X'',1

可以看到 MySQL 5.6 XA 事务和普通事务的binlog是一样的,并没有体现 xa prepare。而到了 MySQL 5.7 XA 事务的binlog和 普通的事务是完全不同的,XA Prepare有单独的Log event类型,有自己的Gtid,当开启semi-sync的情况下,MySQL 5.7 执行 XA prepare 时会等备库回复后才返回结果给客户端,这样XA prepare执行完就是安全的。

通过以上分析可以看出 MySQL 5.7在XA事务安全性方面做了很大的改进。

事物相关命令

普通事物命令

1
2
3
4
5
6
7
set autocommit = 0;
begin;

INSERT INTO `test_message`(`traceId`, `status`, `userId`, `createTime`, `modifyTime`) VALUES (1004641211555425604490, 'test', 'dxy_810axyp8', '2019-04-16 22:40:04', '2018-07-19 11:05:48');

commit;
rollback;

XA事物命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SHOW VARIABLES like '%support_xa%';
xa start 'xid';

INSERT INTO `push_message_01`.`test_message_1` ( `traceId`, `status`, `userId`, `createTime`, `modifyTime` )
VALUES
( 10000315554324112057, 'test', 'dxy_810axyp8', '2019-04-17 01:01:52', '2018-07-19 11:05:48' );

XA END 'xid';
xa prepare 'xid';

-- mysql启动恢复执行的命令,用于恢复XA提交
xa RECOVER;

xa commit 'xid';
xa ROLLBACK 'xid';

binlog相关命令

1
2
3
4
5
-- 查看当前的binlog信息
show master status;
-- 查看所有的binlog事件
show binlog events;
show binlog events in 'mysql-bin.000095';