修复MySQL中人人头疼的Bug,看这篇就够!

咱们看到在MySQL 5.7版本里大量遗留不少年的bug都被fix掉了,bug#12161就是其中一个,该bug在2005年第一次report到Bug list上,十年以后终于在MySQL 5.7.7 第一个RC版本被fix了。mysql

Bug描述

当咱们显式开启一个XA事务,执行操做,并完成XA PREPARE后,若是Kill session或者主动断开再重连执行XA RECOVER,以前的这个XA事务就会直接丢失掉了。git

例如:sql

mysql> XA BEGIN 'abc';
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO t1 VALUES (1,2,3);
Query OK, 1 row affected (0.00 sec)

mysql> XA END 'abc';
Query OK, 0 rows affected (0.00 sec)

mysql> XA PREPARE 'abc';
Query OK, 0 rows affected (0.00 sec)

mysql> Ctrl-C -- exit!
Aborted

mysql> XA RECOVER;
Empty set (0.00 sec)
复制代码

有趣的是,若是在XA PREPARE后把实例KILL掉,是能够经过XA RECOVER恢复的:markdown

mysql> XA RECOVER;
+----------+--------------+--------------+------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+------+
| 1 | 3 | 0 | abc |
+----------+--------------+--------------+------+
1 row in set (0.00 sec)

mysql> XA COMMIT 'abc';
Query OK, 0 rows affected (0.00 sec)
复制代码

虽然实例异常重启能够恢复事务,但引入的另一个问题是:事务变动的binlog丢失,致使主备数据不一致。session

bug产生的缘由也很简单:在退出session时,线程老是会去无条件的回滚掉本身还没有提交的事务。函数

官方修复

持久化

为了解决这个问题,将XA的两阶段记录到了Binlog中;编码

对于上文描述的序列,当执行到XA PREPARE时,记录第一阶段的binlog,以下:spa

Query event : XA START X'616263',X'’,1  // 这里的'616262'便是'abc'的十六进制编码
Table_map event
Write_rows event
Query event:XA END X'616263',X'',1
XA_prepare event: XA PREPARE X'616263',X'’,1
复制代码

这时候该XA事务同时在InnoDB层(事务处于Prepare状态,Redo持久化到磁盘)和Server层都有持久化信息。线程

其中XA_PREPARE事件是新引入的事件类型(内部类为XA_prepare_event),之后版本升级须要注意到这个低版本不兼容事件。rest

而后再执行XA COMMIT ‘abc’,产生新的事件:

Query event:XA COMMIT X'616263',X'',1
复制代码

若是执行XA ROLLBACK,则记录:

Query event:XA ROLLBACK X'616263',X'',1
复制代码

因为XA PREPARE和XA COMMIT是分开执行的,所以在这两个事件中间可能存在别的事务,备库复制线程须要处理这种状况。

为了实现XA PREPARE写binlog,对binlog_prepare进行了扩展,这里会调用mysql_bin_log.commit, 将cache中的binlog刷到文件中。

Tips:XID能够包含三个部分:gtrid, [, bqual [, format ID]],其中gtrid是必选的,表示全局标识,bqual是分支标识,默认为空’‘,format ID是一个unsigned整型,默认值为1,在上例中,咱们只指定了gtrid为’abc’,所以bqual段和format ID均为默认值。更具体的描述参考官方文档。

如何恢复

当会话断开时(例如kill session或者一次干净的shutdown/restart操做),咱们必需要能恢复该事务,以前的逻辑是在cleanup时,直接回滚全部的活跃事务。在新版本中,对XA PREPARE的事务作了特殊处理(THD::cleanup),若是处于Prepare状态,就将事务的in_recovery设置为TRUE,并更新到hash表transaction_cache中(transaction_cache_detach),该hash表用于维护全部XA事务。

对于非XA的活跃事务,在会话断开时,依然采用回滚策略。

当重连客户端后,咱们能够直接执行 XA COMMIT ‘abc’,这时候会经过XID关键字去搜索transaction_cache并将对应的事务提交掉。

同时BINLOG的状态要保持一致,若是会话断开前的XA PREPARE没有记录Binlog, 重连后执行XA COMMIT也不该该记录。

备库复制

因为XA PREPARE和XA COMMIT是分开记录的,当碰到XA COMMIT时,备库采用等待以前的事务所有完成,而后再执行的方式(至关于退化到串行)。

另外,咱们知道在一个正常的会话过程当中,老是为其cache一个事务对象,新的事务会重用这个事务对象,避免屡次分配;而XA事务的COMMIT和PREPARE是分离的,须要为XA事务单独分配事务对象。所以复制线程执行XA START时,将其拥有的事务对象临时保存起来(detach_native_trx),当执行到XA_prepare_log_event事件时,再将其恢复给复制线程,同时XA事务对象关闭read view,将is_recovered设置为TRUE(函数innodb_replace_trx_in_thd)。

随后复制线程在执行到XA COMMIT时直接根据XID找到对应的XA事务进行提交。

参考:

WL#6860 Binlogging XA-prepared transaction Github:git show f4c37f7aea732763947980600c6882ec908a54a0 MySQL 5.7.7-RC

有任何问题欢迎留言交流~

看到这里的小伙伴,若是你喜欢这篇文章的话,别忘了转发、收藏、留言互动

若是对文章有任何问题,欢迎在留言区和我交流~

最近我新整理了一些Java资料,包含面经分享、模拟试题、和视频干货,若是你须要的话,欢迎留言or私信我

还有,关注我!关注我!关注我!