MySQL XA限制与修复

背景知识

MySQL的XA分为内部XA和外部XA,采用的协议是经典的2PC(2-Phase-Commit)

MySQL内部XA主要用于多个存储引擎之间的事务处理,由binlog作为Transaction Coordinator,事务提交过程中协调各个参与者(事务存储引擎)prepare,commit/rollback,各个事务存储引擎在prepare阶段基本做完了所有的事情(除了写commit标志,释放资源),对于InnoDB存储引擎,提交一个事务的流程如下:(图片来自登博)

commit

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)

欢迎讨论:

percona bug 1204353

《MySQL XA限制与修复》上有1条评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注