MySQL 事务基础知识(redo log和binlog的xa-2pc)

关于XA

为了减少行业内不必要的对接成本,需要制定标准化的处理模型及接口标准,于是X/Open组织定义分布式事务处理模型DTP(Distributed Transaction Processing Reference Model),主要包含四个角色:

  1. AP是应用程序,是事务发起和结束的地方;
  2. RM是资源管理器,主要负责管理每个数据库的连接数据源,通常一个数据库就是一个资源管理器。;
  3. TM是事务管理器,负责事务的全局管理,包括事务的生命周期管理和资源的分配协调等。
  4. CRM(通讯资源管理器), 即消息中间件

其示例图如下:
DTP MODEL
在DTP的基础上X/Open组织提出的分布式事务的接口规范(X代表transaction; A代表accordant)。XA是(全局)事务管理器(TM: Transaction Manager)和(局部)资源管理器(RM: Resource Manager)之间的接口。XA接口函数由RM提供,TM调用这些函数来通知数据库事务的开始、结束以及提交、回滚等,由此衍生出了1PC,2PC,3PC(three phase commit)。其中2PC就是将事务完成分为两步走,即准备阶段和提交阶段。

MySQL中的XA

如Oracle,MySQL等支持XA。MySQL中XA分为内部XA和外部XA,外部XA主要是MySQL自身作为RM,配合一些三方TM(如DTM,阿里的Seata等),内部XA很好理解,就是MySQL内部的对XA的实现(MySQL service层提供TM,存储引擎和日志(binlog)等作为RM)。

外部XA使用
XA {START|BEGIN} xid [JOIN|RESUME]

XA END xid [SUSPEND [FOR MIGRATE]]

XA PREPARE xid

XA COMMIT xid [ONE PHASE]

XA ROLLBACK xid

XA RECOVER [CONVERT XID]

详情请看官网:XA transation

内部XA概述

关于内部XA的事务管理器(TM)的选择,在实例启动时决定使用何种XA模式,源码如下:

tc_log = &tc_log_dummy;
if (total_ha_2pc > 1 || (1 == total_ha_2pc && opt_bin_log))
  {
    if (opt_bin_log)   //当binlog打开的时候tc_log初始化为&mysql_bin_log
      tc_log= &mysql_bin_log;  //(binlog.h>>extern MYSQL_PLUGIN_IMPORT MYSQL_BIN_LOG mysql_bin_log;)
    else
      tc_log= &tc_log_mmap;
  }
else
    tc_log= &tc_log_dummy;	

可以看到有三种选择:

1.如果开启了binlog的话,若打开binlog,则TM为mysql_bin_log对象
2. 否则,当有超过一个支持事务的存储引擎参与,则TM为tc_log_mmap对象
3. 否则,使用tc_log_dummy对象,它是一个空实现,这个时候就不存在xa-2pc了。
这三者都是TC_LOG的子类,如下图:
在这里插入图片描述
这里就不多说了,主要聊下当你开启binlog时,此时我们知道binlog中的数据关系到增量数据恢复和从节点数据与主节点数据的一致性。所以如何保证binlog数据(server层)与innodb的redo log数据(存储引擎层)一致呢?MySQL采取的策略就是使用XA-2PC。

MySQL中的XA之binlog

废话不多说,在5.7.6版本之后,MySQL团队引入了淘宝团队对这部分的优化,XA事务的提交流程如下(V5.7.6之前版本的事务提交流程解析):

PREPARE phase:

  • InnoDB Prepare (入口函数innobase_xa_prepare --> trx_prepare): 更新InnoDB的undo回滚段,将其设置为Prepare状态(TRX_UNDO_PREPARED),并将redo log buffer 写到 OS Cache。

  • Binlog Prepare 设置thd->durability_property= HA_IGNORE_DURABILITY, 表示在innodb prepare时,不刷redo log。

如下图:

在这里插入图片描述
COMMIT phase:
这一阶段可以分为三步:1》write binlog to os cache & fsync redo log;2》 fsync binlog;3》 Innodb commit ,如下图所示:
在这里插入图片描述可以看到,通过xa-2pc,解决了redo log 和bin log数据不一致的问题。

MySQL中的隐式XA之binlog的crash recovery

如上图所示,MySQL崩溃修复主要根据实物的状态来判断:

1>对于TRX_COMMITTED_IN_MEMORY的事务,清除回滚段,然后将事务设为TRX_NOT_STARTED,主要是在准备阶段崩溃的情况;

2>对于TRX_NOT_STARTED的事务,表示事务已经提交,不做处理;

3>对于TRX_PREPARED的事务,在提交阶段崩溃时发生,此时innodb_commit还没有完成,但数据已写入日志。要根据binlog来决定事务的命运:

  1. 扫描最后一个 binlog,提取xid(标识binlog中的第几个event);
  2. xid也会写到redo中,将redo中prepared状态的xid,去跟binlog中的xid 比较 , 如果binlog中存在,则提交,否则回滚;
    比较规则:通过redo和binlog 共有的xid数据字段 到binlog中找对应的事务并查看此事务, 最后有xid event则完整(row格式), 最后有commit则完整(statement格式)
    注: 之所以提取binlog最后一个binlog的xid,这是因为mysql日志是顺序写(非随机写),并且事务不会跨binlog文件,允许文件大小略大于参数max_binlog_size的值(默认1G)。
    4>对于TRX_ACTIVE的事务,回滚。

MySQL中的XA之binlog,redo log的group commit

xa-2pc解决了数据一致性问题,但如果每个事务独立提交写磁盘(I/O),会很浪费时间,MySQL通过事务组提交的方式来提高性能。
注:一下内容引用自文章:MYSQL-GroupCommit 和 2pc提交 ,大神总结的很好很细致。

《》《》《》《》《》《》-------------《》《》《》《》《》《》
组提交(group commit)是MYSQL处理日志的一种优化方式,主要为了解决写日志时频繁刷磁盘的问题。组提交伴随着MYSQL的发展不断优化,从最初只支持redo log 组提交,到目前5.6官方版本同时支持redo log 和binlog组提交。组提交的实现大大提高了mysql的事务处理性能,下文将以innodb 存储引擎为例,详细介绍组提交在各个阶段的实现原理。

  1. redo log的组提交
    WAL(Write-Ahead-Logging)是实现事务持久性的一个常用技术,基本原理是在提交事务时,为了避免磁盘页面的随机写,只需要保证事务的redo log写入磁盘即可,这样可以通过redo log的顺序写代替页面的随机写,并且可以保证事务的持久性,提高了数据库系统的性能。虽然WAL使用顺序写替代了随机写,但是,每次事务提交,仍然需要有一次日志刷盘动作,受限于磁盘IO,这个操作仍然是事务并发的瓶颈。
    组提交思想是,将多个事务redo log的刷盘动作合并,减少磁盘顺序写。Innodb的日志系统里面,每条redo log都有一个LSN(Log Sequence Number),LSN是单调递增的。每个事务执行更新操作都会包含一条或多条redo log,各个事务将日志拷贝到log_sys_buffer时(log_sys_buffer 通过log_mutex保护),都会获取当前最大的LSN,因此可以保证不同事务的LSN不会重复。那么假设三个事务Trx1,Trx2和Trx3的日志的最大LSN分别为LSN1,LSN2,LSN3(LSN1<lsn2<lsn3),它们同时进行提交,那么如果trx3日志先获取到log_mutex进行落盘,它就可以顺便把[lsn1—lsn3]这段日志也刷了,这样trx1和trx2就不用再次请求磁盘io。组提交的基本流程如下: </lsn2<lsn3),它们同时进行提交,那么如果trx3日志先获取到log_mutex进行落盘,它就可以顺便把[lsn1—lsn3]这段日志也刷了,这样trx1和trx2就不用再次请求磁盘io。
    组提交的基本流程如下:

    1:获取 log_mutex,
    2:若lsn<=flushed_to_disk_lsn,表示日志已经被刷盘,跳转5
    3:若lsn<=current_flush_lsn,表示日志正在刷盘中,跳转5后进入等待状态
    4: 将小于lsn的日志刷盘(fsync)
    5:退出log_mutex
    备注:lsn是日志序列号,flushed_to_disk_lsn和current_flush_lsn分别表示已刷盘的LSN和正在刷盘的LSN。

  2. redo log 组提交优化
    我们知道,在开启binlog的情况下,prepare阶段,会对redo log进行一次刷盘操作(innodb_flush_log_at_trx_commit=1),确保对data页和undo 页的更新已经刷新到磁盘;commit阶段,会进行刷binlog操作(sync_binlog=1),并且会对事务的undo log从prepare状态设置为提交状态(可清理状态)。通过两阶段提交方式(innodb_support_xa=1),可以保证事务的binlog和redo log顺序一致。二阶段提交过程中,mysql_binlog作为协调者,各个存储引擎和mysql_binlog作为参与者。故障恢复时,扫描最后一个binlog文件(进行rotate binlog文件时,确保老的binlog文件对应的事务已经提交),提取其中的xid;重做检查点以后的redo日志,读取事务的undo段信息,搜集处于prepare阶段的事务链表,将事务的xid与binlog中的xid对比,若存在,则提交,否则就回滚。
    通过上述的描述可知,每个事务提交时,都会触发一次redo flush动作,由于磁盘读写比较慢,因此很影响系统的吞吐量。淘宝童鞋做了一个优化,将prepare阶段的刷redo动作移到了commit(flush-sync-commit)的flush阶段之前,保证刷binlog之前,一定会刷redo。这样就不会违背原有的故障恢复逻辑。移到commit阶段的好处是,可以不用每个事务都刷盘,而是leader线程帮助刷一批redo。如何实现,很简单,因为log_sys->lsn始终保持了当前最大的lsn,只要我们刷redo刷到当前的log_sys->lsn,就一定能保证,将要刷binlog的事务redo日志一定已经落盘。通过延迟写redo方式,实现了redo log组提交的目的,而且减少了log_sys->mutex的竞争。目前这种策略已经被官方mysql5.7.6引入。

  3. 两阶段提交
    在单机情况下,redo log组提交很好地解决了日志落盘问题,那么开启binlog后,binlog能否和redo log一样也开启组提交?首先开启binlog后,我们要解决的一个问题是,如何保证binlog和redo log的一致性。因为binlog是Master-Slave的桥梁,如果顺序不一致,意味着Master-Slave可能不一致。MYSQL通过两阶段提交很好地解决了这一问题。Prepare阶段,innodb刷redo log,并将回滚段设置为Prepared状态,binlog不作任何操作;commit阶段,innodb释放锁,释放回滚段,设置提交状态,binlog刷binlog日志。出现异常,需要故障恢复时,若发现事务处于Prepare阶段,并且binlog存在则提交,否则回滚。通过两阶段提交,保证了redo log和binlog在任何情况下的一致性。

  4. binlog的组提交
    回到上节的问题,开启binlog后,如何在保证redo log-binlog一致的基础上,实现组提交。因为这个问题,5.6以前,mysql在开启binlog的情况下,无法实现组提交,通过一个臭名昭著的prepare_commit_mutex,将redo log和binlog刷盘串行化,串行化的目的也仅仅是为了保证redo log-Binlog一致,但这种实现方式牺牲了性能。这个情况显然是不能容忍的,因此各个mysql分支,mariadb,facebook,perconal等相继出了补丁改进这一问题,mysql官方版本5.6也终于解决了这一问题。由于各个分支版本解决方法类似,我主要通过分析5.6的实现来说明实现方法。
    binlog组提交的基本思想是,引入队列机制保证innodb commit顺序与binlog落盘顺序一致,并将事务分组,组内的binlog刷盘动作交给一个事务进行,实现组提交目的。binlog提交将提交分为了3个阶段,FLUSH阶段,SYNC阶段和COMMIT阶段。每个阶段都有一个队列,每个队列有一个mutex保护,约定进入队列第一个线程为leader,其他线程为follower,所有事情交由leader去做,leader做完所有动作后,通知follower刷盘结束。binlog组提交基本流程如下:

FLUSH 阶段

  1.  持有Lock_log mutex [leader持有,follower等待]
    
  2.  获取队列中的一组binlog(队列中的所有事务)
    
  3. leader 调用 ha_flush_logs 通知Innodb做一次 redo fsync,即一次将所有线程的 redo log 刷盘,很耗时
    
  4. 将binlog buffer write到os cache
    
  5. 通知dump线程dump binlog
    

SYNC阶段

  1.  释放Lock_log mutex,持有Lock_sync mutex[leader持有,follower等待]
    
  2.  将一组binlog 落盘(fsync动作,很耗时,假设sync_binlog为1)
    

COMMIT阶段

  1.  释放Lock_sync mutex,持有Lock_commit mutex[leader持有,follower等待]
    
  2.  遍历队列中的事务,逐一进行innodb commit
    
  3.  释放Lock_commit mutex
    
  4.  唤醒队列中等待的线程
    

说明:由于有多个队列,每个队列各自有mutex保护,队列之间是顺序的,约定进入队列的一个线程为leader,因此FLUSH阶段的leader可能是SYNC阶段的follower,但是follower永远是follower。

题外话-MySQL关于XA的描述

A global transaction involves several actions that are transactional in themselves, but that all must either complete successfully as a group, or all be rolled back as a group. In essence, this extends ACID properties “up a level” so that multiple ACID transactions can be executed in concert as components of a global operation that also has ACID properties. (As with nondistributed transactions, SERIALIZABLE may be preferred if your applications are sensitive to read phenomena. REPEATABLE READ may not be sufficient for distributed transactions.)

In some cases, a global transaction might use one-phase commit (1PC). For example, when a Transaction Manager finds that a global transaction consists of only one transactional resource (that is, a single branch), that resource can be told to prepare and commit at the same time.

见于mysql 5.7 官方文档
主要表达了两个意思:
1:innodb事务隔离级别是可重复读时可能不太能满足分布式事务的需要,这个事务隔离级别调整要看业务对数据的敏感程度;
2:当TM发现事务中只有一个RM时,就会退化成1PC。

参考

1】:MySQL undo,redo,2PC,恢复思维导图
2】:mysql 内部xa(两阶段提交)
3】: MYSQL-GroupCommit 和 2pc提交
4】:Mysql事务—内部XA的两阶段提交(2pc)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值