背景知识
MySQL的XA分为内部XA和外部XA,采用的协议是经典的2PC(2-Phase-Commit)
MySQL内部XA主要用于多个存储引擎之间的事务处理,由binlog作为Transaction Coordinator,事务提交过程中协调各个参与者(事务存储引擎)prepare,commit/rollback,各个事务存储引擎在prepare阶段基本做完了所有的事情(除了写commit标志,释放资源),对于InnoDB存储引擎,提交一个事务的流程如下:(图片来自登博)
binlog本身也会prepare/commit,但是binlog的prepare阶段不做任何事情(意味着不可能失败),代码中可以看见binlog_prepare函数是空的,写入binlog的过程是在commit阶段做的(在其他事务存储引擎commit之前),binlog具有“原子”性:每个事务产生的binlog是连续、完整的写入到binlog文件中,事务之间不会交叉写(与InnoDB的redo log不同),因此在crash recovery阶段,如果redo log中未提交的事务能够在binlog中发现,说明其已经prepare成功,可以直接提交,而如果redo中未提交事务不在binlog中,直接回滚事务,这样能够保证binlog和存储引擎数据的一致。
MySQL XA限制
MySQL外部XA需要外部APP作为Transaction Coordinator,DBMS成为资源管理者,此时binlog成为了一个参与者,由于binlog的prepare阶段没有写任何日志,MySQL外部XA存在如下缺陷:
server crash后重启,XA RECOVER可以恢复出未提交的XA PREPARED事务,但是提交时无法记录binlog(crash时binlog cache丢失),这就使得binlog和存储引擎数据不一致,会引起复制中断
这两个问题在mysql xa limitations有提到,有关MySQL XA实现可以参考登博的一篇博客
想要彻底修复这个问题需要修改binlog的逻辑,这是一个大工程,而之前设计的binlog不写prepare日志实际上是一个优化措施,毕竟crash这种事情不会很多,必须使用MySQL外部XA的场景不多,因此使用MySQL外部XA且crash的用户需求被忽略了。。。
本文也不打算修复这个问题,是为了修复另外一个问题:
session执行xa prepare之后连接断掉,XA_PREPARED事务会自动回滚
这是MySQL server层的行为,连接只要断掉,未提交的事务就会自动回滚,但这对于XA PREPARED事务有些不可接受,竟然XA PREPARED,就应该始终能够通过“XA RECOVER;”找到,重新连接后应该可以再次commit(即使crash后都可以恢复并重新执行“XA COMMIT”),这也是RDS团队提出的一个需求
解决方案
在MySQL 5.0的时候就有用户提出这个问题,官方一直在说进行中,但直到5.6都没有给出解决方案,应该优先级设置得比较低,看来只能自己动手fix了
a) feng shao在MySQL bug12161中提出了一个ha_semi_rollback_trans的方案,但是存在两个问题:
1、重新连接后,虽然能够通过“XA RECOVER;”找回事务,但是执行“XA COMMIT;”之后binlog会丢失
2、只针对InnoDB引擎,通用性不够
为此,提出另外一个方案:session连接断开时,挂起对应的*thd*,并运行其他session可以“代理” XA COMMIT/ROLLBACK
b) patch中所有修改都在MySQL Server层,主要有一下几处:
1、session连接断开时,判断连接中是否存在XA PREPARED状态的未提交事务,若存在,将其对应的*thd*保存到动态数组xa_prepared_threads中
2、trans_xa_commit,判断xid是否属于xa_prepared_threads中的某个*thd*,如果是,从xa_prepared_threads移除对应的*thd*, 并提交事务,unlink thd
3、trans_xa_rollback,判断xid是否属于xa_prepared_threads中的某个*thd*,如果是,从xa_prepared_threads移除对应的*thd*, 并回滚事务,unlink thd
4、kill session,判断xid是否属于xa_prepared_threads中的某个*thd*,如果是,从xa_prepared_threads移除对应的*thd*, 并unlink thd
patch效果
a) 测试表结构
mysql> reset master; Query OK, 0 rows affected (0.34 sec) mysql> show master logs; +------------------+-----------+ | Log_name | File_size | +------------------+-----------+ | mysql-bin.000001 | 107 | +------------------+-----------+ 1 row in set (0.00 sec) mysql> show create table test.t\G *************************** 1. row *************************** Table: t Create Table: CREATE TABLE `t` ( `id` int(11) NOT NULL AUTO_INCREMENT, `num` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=gbk 1 row in set (0.00 sec) mysql> select * from test.t; +----+------+ | id | num | +----+------+ | 1 | 1 | +----+------+ 1 row in set (0.00 sec) |
b) 执行xa prepared后断开连接
mysql> xa start 'xa_2'; Query OK, 0 rows affected (0.00 sec) mysql> insert into test.t(num) values(2); Query OK, 1 row affected (0.00 sec) mysql> xa end 'xa_2'; Query OK, 0 rows affected (0.00 sec) mysql> xa prepare 'xa_2'; Query OK, 0 rows affected (0.00 sec) mysql> quit Bye |
c) 重新连接后通过show processlist/xa recover可以查看到pending xa事务(XA_PREPARED状态)
$bin/mysql -uroot -S run/mysql.sock Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 4096 Server version: 5.5.18-tb3633-log Source distribution Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> show processlist\G *************************** 1. row *************************** Id: 4095 User: root Host: localhost db: NULL Command: Killed Time: 17 State: xa prepared, waiting for xa commit/rollback Info: NULL Rows_sent: 0 Rows_examined: 0 Rows_read: 2 *************************** 2. row *************************** Id: 4096 User: root Host: localhost db: NULL Command: Query Time: 0 State: NULL Info: show processlist Rows_sent: 0 Rows_examined: 0 Rows_read: 1 2 rows in set (0.00 sec) mysql> xa recover; +----------+--------------+--------------+------+ | formatID | gtrid_length | bqual_length | data | +----------+--------------+--------------+------+ | 1 | 4 | 0 | xa_2 | +----------+--------------+--------------+------+ 1 row in set (0.00 sec) |
d) ‘代理’提交断开连接的xa prepared事务
mysql> xa commit 'xa_2'; Query OK, 0 rows affected (0.00 sec) mysql> select * from test.t; +----+------+ | id | num | +----+------+ | 1 | 1 | | 2 | 2 | +----+------+ 2 rows in set (0.00 sec) mysql> xa recover; Empty set (0.00 sec) mysql> show processlist\G *************************** 1. row *************************** Id: 4096 User: root Host: localhost db: NULL Command: Query Time: 0 State: NULL Info: show processlist Rows_sent: 0 Rows_examined: 0 Rows_read: 3 1 row in set (0.00 sec) |
e) commit 后binlog成功写入,如果在xa commit前 crash将丢失binlog
mysql> show binlog events in 'mysql-bin.000001'; +------------------+-----+-------------+-----------+-------------+----------------------------------------------+ | Log_name | Pos | Event_type | Server_id | End_log_pos | Info | +------------------+-----+-------------+-----------+-------------+----------------------------------------------+ | mysql-bin.000001 | 4 | Format_desc | 1 | 107 | Server ver: 5.5.18-tb3633-log, Binlog ver: 4 | | mysql-bin.000001 | 107 | Query | 1 | 171 | BEGIN | | mysql-bin.000001 | 171 | Table_map | 1 | 212 | table_id: 186 (test.t) | | mysql-bin.000001 | 212 | Write_rows | 1 | 250 | table_id: 186 flags: STMT_END_F | | mysql-bin.000001 | 250 | Query | 1 | 315 | COMMIT | +------------------+-----+-------------+-----------+-------------+----------------------------------------------+ 5 rows in set (0.00 sec) |
patch:
xa_prepared_disconnect.patch(基于Percona 5.5.18)
《MySQL XA限制与修复》上有1条评论