ITPub博客

首页 > > > MySQL XA之从天而降的匿名事务

MySQL XA之从天而降的匿名事务

作者:u_ea6ef15d346a 时间:2019-11-08 17:39:57 0 删除 编辑

前言

在网易互联网业务中,网易杭研自研的MySQL中间件DDB大量使用,DDB的分布式事务基于MySQL XA,由于MySQL 5.7修改了XA事务记录Binlog的逻辑,导致出现大量corner case,DDB下的MySQL实例也深受其害,典型的问题就是XA事务引发的复制错误,包括加锁超时,复制夯住等。

在之前的文章中,提到网易某些业务使用MyRocks作为延迟从库来提升核心数据的安全性。在延迟从库使用过程中,也遇到了与XA事务相关的一些问题。一个典型的案例就是上游从库(db-4)产生的匿名事务导致延迟从库(下面案例中的db-5)复制中断。

本文着重分析为什么在GTID_MODE为ON的情况下,还会产生匿名事务。


线上问题:

延迟从库复制中断,错误日志为:Got fatal error 1236 from master when reading data from binary log: 'Cannot replicate anonymous transaction when AUTO_POSITION = 1。

错误日志的意思是gtid_mod=on的情况下,不能执行 Anonymous_GTID 的事务。

复制的拓扑如下:

--> db-3:4332 (主库)

--> db-4:4332 (从库) ——>出现因Lock wait timeout exceeded复制中断

-->db-5:4336 (延迟从库) ------>匿名事务导致复制中断

延迟从库的上游主库复制出了问题,加锁超时导致复制中断,并且因为开启了slave-preserve-commit-order参数,导致有2个xa commit事务提交失败。

根据错误日志中提示的binlog文件,解析该binlog文件,内容如下:

可以看到被阻塞提交的事务在db-4的binlog中变成了匿名事务。这样在延迟从库db-5上回放就报了文章开头日志中的错误。

产生匿名事务的条件

GTID 的产生是在 ordered_commit()函数的flush 阶段完成的,产生GTID_log_event的函数是MYSQL_BIN_LOG::write_gtid ,

Gtid_log_event的构造函数如下:

如果条件( thd->owned_gtid.sidno > 0)不满足,就会创建匿名gtid。这个问题的关键是 thd->owned_gtid.sidno > 0 何时不满足了呢?

首先线下先复现出该问题,这样方便分析,复现方法如下:

1、master上跑sysbenchxa (sysbench_xa是网易杭研改造开源sysbench,将普通事务改造成xa事务并可以设置xa事务的工具,专门用于模拟公司内部xa事务的业务场景)

2、master 上执行以下操作:

                session 1                      |                  session 2
xa start 'xa1';                                |
update t2 set b =12 where a =1;                |
xa end 'xa1';                                  |
xa prepare 'xa1';                              |
exit session                                   |
reconnect session                              |
set session sql_log_bin = 0;                   |
xa commit 'xa1';                               |                                   
                                               |            xa start 'xa2';
                                               |            update t2 set b = 13 where a =1;
                                               |            xa end 'xa2';
                                               |            xa prepare 'xa2';
                                               |            xa commit 'xa2';

因为xa commit 'xa1' 没有记入binlog,所以从库上a=1 这条记录会一直持有行锁, 当slave执行xa2时会加锁失败,重试10次后sql_thread停止。在复现过程中有以下发现:

1、当有xa commit被阻塞提交时,从库就会出现匿名事务。当被阻塞的事务没有xa commit时就不会出现匿名事务。
2、worker在重试加锁时,从库binlog并没有出现匿名事务,当sql_thread停止后binlog才出现匿名事务。

首先分析出thd->owned_gtid.sidno何时被清0,然后分析出何处产生匿名事务。

thd->owned_gtid.sidno 是在THD::clear_owned_gtids函数中被清零,匿名事务的产生上文介绍了是在MYSQL_BIN_LOG::write_gtid中。

还是利用上面的复现方法,当出现锁超时后利用gdb attach到从库实例,并在THD::clear_owned_gtids和MYSQL_BIN_LOG::write_gtid 打断点,set scheduler-locking on避免gdb切换到别的线程。

需要注意的是,xa2由于xa1的锁未释放,导致重试10次后退出。下面分析的worker线程不是xa1和xa2所在的线程,而是正常执行sysbench_xa工具产生的事务的worker线程。假设有个事务为xa3。其事务提交顺序在xa2之后。

xa3事务所在worker线程清空sidno的逻辑

thd->owned_gtid.sidno 清零的调用栈如下,

owned_gtid.gno与error.log中的输出是对应的:

因为被加锁超时的worker阻塞住了,所以要执行xa commit的xa3所在worker并没有顺利执行ordered_commit函数,

wait_for_its_turn函数执行成功返回0,不成功返回非0,这里被阻塞提交的xa3事务返回非0 ,回到mysql_execute_command,

进入trans_rollback_stmt函数,接着进入MYSQL_BIN_LOG::rollback函数,rollback函数也会进入ordered_commit函数的,但是在下面的地方直接返回了:

回到rollback函数后,进入update_on_rollback函数,

update_on_rollback函数逻辑包括clear_own_gtid函数的调用。

xa3所在worker线程退出逻辑

xa3所在的worker线程经过上面的代码逻辑会退出了正常的apply event的循环,进入到线程退出的逻辑中,其中一步是执行thd::release_resources()函数释放资源,这一波处理的调用栈如下:

同样是进入了ordered_commit函数,但此时 has_commit_order_manager函数返回0,所以进入后面的创建gtid函数内。

为啥has_commit_order_manager()返回false了呢:

在进入release_resources之前,有这么一行代码:

所以has_commit_order_manager()函数false,ordered_commit可以继续执行后面的逻辑。

这个调用栈是在worker收到协调者发来的STOP信号后清理资源准备退出的过程,Gtid_log_event的构造函数上文已经介绍了,因为上一步thd->owned_gtid.sidno已经被清零了,所以进入匿名GTID创建的逻辑,从库binlog文件就出现了匿名事务。

并发执行的其他非阻塞普通事务commit所在worker线程推出逻辑

上文说到在线下复现时有2个发现,第一个发现就是被阻塞提交的事务如果执行的不是xa commit,binlog中并没有出现匿名事务。线下调试发现了普通事务也是有两次进入MYSQL_BIN_LOG::rollback函数的,调用栈跟xa commit一致,但是都没有进入MYSQL_BIN_LOG::ordered_commit函数,代码执行到下图位置直接goto end,

cache_mngr->is_binlog_empty() 返回true,直接进入goto end,ordered_commit函数没有执行。

看起来这又是一例XA事务prepare和commit分别记录binlog event导致xa commit在处理一些特殊场景时出问题。

总结

本文主要介绍了如何复现线上问题以及分析了该问题发生原因,也许可以在worker线程退出清理资源的代码逻辑中增加判断来规避此问题。

原文作者:;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2019-12-12

  • 博文量
    120
  • 访问量
    483