上一篇文章我介绍了在关闭binlog的情况下,事务提交的大概流程。之所以关闭binlog,是因为开启binlog后事务提交流程会变成两阶段提交,这里的两阶段提交并不涉及分布式事务,当然mysql把它称之为内部xa事务(Distributed Transactions),与之对应的还有一个外部xa事务。内部xa事务我理解主要是mysql内部为了保证binlog与redo log之间数据的一致性而存在的,这也是由其架构决定的(binlog在mysql层,而redo log 在存储引擎层);而外部xa事务则是指支持多实例分布式事务,这个才算是真正的分布式事务。既然是xa事务,必然涉及到两阶段提交,对于内部xa而言,同样存在着提交的两个阶段。下文会结合源码详细解读内部xa的两阶段提交过程,以及各种情况下,mysqld crash后,mysql如何恢复来保证事务的一致性。

测试环境:
OS:windows
DB:mysql 5.6.12
engine:innodb
 
配置文件参数:
log-bin=D:\chuck\mysql\log\5-6-12\mysql-bin
binlog_format=ROW
set autocommit=0;
sync_binlog=1;
innodb_flush_log_at_trx_commit=1;
innodb_support_xa=1;
 
测试前置条件:
create table tt(col1 int, col2 varchar(100));
 
测试语句:
insert into tt values(1, 'abcdef');
commit;
 
     打开binlog选项后,执行事务提交命令时,就会进入两阶段提交模式。两阶段提交分为prepare阶段和commit两个阶段。流程如下 :这里面涉及到两个重要的参数:innodb_flush_log_at_trx_commit和sync_binlog,参数可以设置不同的值,具体可以查看mysql的帮助手册。我这里设置的是双一模式(innodb_flush_log_at_trx_commit=1,sync_binlog=1),不同的模式区别在于,写文件调用write和落盘fsync调用的频率不同,所导致的后果是mysqld 或 os crash后,不严格的设置可能会丢失事务的更新。双一模式是最严格的模式,这种设置情况下,单机在任何情况下不会丢失事务更新。
prepare阶段:
    1.设置undo state=TRX_UNDO_PREPARED; //trx_undo_set_state_at_prepare调用
    2.刷事务更新产生的redo日志;【步骤1产生的redo日志也会刷入】
    
commit阶段:
   1.将事务产生的binlog写入文件,刷入磁盘;
   2.设置undo页的状态,置为TRX_UNDO_TO_FREE或TRX_UNDO_TO_PURGE;  // trx_undo_set_state_at_finish调用

3.记录事务对应的binlog偏移,写入系统表空间; //trx_sys_update_mysql_binlog_offset调用

    
    下面这部分是我抽象出来的源码调用部分,大家可以通过单步调试方式,在关键函数中设置断点,来详细了解这个过程。
===========
 prepare阶段
===========
MYSQL_BIN_LOG::prepare
    ha_prepare_low
    {
engine:
binlog_prepare
innobase_xa_prepare
mysql:
trx_prepare_for_mysql
{

1.trx_undo_set_state_at_prepare    //设置undo段的标记为TRX_UNDO_PREPARED
                2.设置事务状态为TRX_STATE_PREPARED
                3.trx_flush_log_if_needed  //将产生的redolog刷入磁盘

            }
     }
     
============
commit阶段
============
MYSQL_BIN_LOG::commit
    ordered_commit
   {
1.FLUSH_STAGE
        flush_cache_to_file  //  刷binlog
 
2.SYNC_STAGE
        sync_binlog_file    //Call fsync() to sync the file to disk.
 
3.COMMIT_STAGE
        ha_commit_low
        {
            binlog_commit
            innobase_commit   
                trx_commit(trx) 
                {
                    trx_write_serialisation_history(trx, mtr);  //更新binlog位点,设置undo状态
                    trx_commit_in_memory(trx, lsn); //释放锁资源,清理保存点列表,清理回滚段
                }        
        } 
    }
 
      mysqld可能在任何情况下crash,os也有可能出现问题,另外若机器掉电,mysqld也会同样挂掉。但是即使这样,mysql仍然能保证数据库的一致性。接下来,我会结合上述流程,分析二阶段提交如何保证这点的。下面给出几种常见的场景,
1.prepare阶段,redo log落盘前,mysqld crash
2.prepare阶段,redo log落盘后,binlog落盘前,mysqld crash
3.commit阶段,binlog落盘后,mysqld crash
      对于第一种情况,由于redo没有落盘,毫无疑问,事务的更新肯定没有写入磁盘,数据库的一致性受影响;对于第二种情况,这时候redo log写入完成,但binlog还未写入,事务处于TRX_STATE_PREPARED状态,这是提交还是回滚呢?对于第三种情况,此时,redo log和binlog都已经落盘,只是undo状态没有更新,这种情况也应该提交,因为redo log和binlog已经一致了,当然这只是我的假设,需要通过源码逻辑来验证。
     下面给出了mysqld异常重启后的执行逻辑以及关键的源代码。对于第三种情况,我们可以搜集到未提交事务的binlog event,所以需要提交,与我们假设相符;而对于第二种情况,由于binlog未写入,需要通过执行回滚操作来保证数据库的一致性。
 
异常重启后,如何判断事务该提交还是回滚
1.读binlog日志,获取崩溃时没有提交的event;  //info->commit_list中含有该元素
2.若存在,则对应的事务要提交;否则需要回滚。
 
判断事务提交或回滚源码如下:
 
 
     上面讨论了两阶段提交的基本流程,以及服务器异常crash后,mysql如何重启恢复保证binlog和数据的一致性。简而言之,对于异常的xa事务,若binlog已落盘,则事务应该提交;binlog未落盘,则事务就应该回滚。由于这块涉及到的源代码较多,我也没有看完所有源代码,如有不正确的地方,欢迎指正。
 
//异常重启后,回滚流程
innobase_rollback_by_xid
    rollback_by_xid
trx_rollback_resurrected
    trx_rollback_active
        row_undo
        {
            //从回滚页获取undo记录
            //分析undo记录类型
            if (insert)
                row_undo_ins
            else
                row_undo_mod
        }
 
//异常重启后,提交流程
commit_by_xid
    trx_commit_for_mysql
 
//写binlog接口
handler.cc:binlog_log_row
sql/binlog.cc:commit
mysys/my_sync:my_sync
sql/binlog.cc:sync_binlog_file
handler/ha_innodb.cc:innobase_xa_prepare

mysql源码解读之事务提交过程(二)的更多相关文章

  1. mysql源码解读之事务提交过程(一)

    mysql是一种关系型数据库,关系型数据库一个重要的特性就是支持事务,这是区别于no-sql产品的一个核心特性.当然了,no-sql产品支持键值查询,不能支持sql语句,这也是一个区别.今天主要讨论下 ...

  2. Spring Ioc源码分析系列--Bean实例化过程(二)

    Spring Ioc源码分析系列--Bean实例化过程(二) 前言 上篇文章Spring Ioc源码分析系列--Bean实例化过程(一)简单分析了getBean()方法,还记得分析了什么吗?不记得了才 ...

  3. mysql源码解读之配置文件

    要研究mysql,最好的资源莫过于源码了,所以本人打算通过调试源码的方式来深入理解mysql的点点滴滴.搭建mysql调试环境很简单,从官方下载mysql源码,利用cmake工具生成工程即可.为了方便 ...

  4. MySQL源码之两阶段提交

    在双1的情况下,两阶段提交的过程 环境准备:mysql 5.5.18, innodb 1.1 version配置: sync_binlog=1 innodb_flush_log_at_trx_comm ...

  5. .NET框架源码解读之SSCLI编译过程简介

    前文演示了编译SSCLI最简便的方法(在Windows下): 在“Visual Studio 2005 Command Prompt”下,进入SSCLI的根目录: 运行 env.bat 脚本准备环境: ...

  6. storm源码分析之topology提交过程

    storm集群上运行的是一个个topology,一个topology是spouts和bolts组成的图.当我们开发完topology程序后将其打成jar包,然后在shell中执行storm jar x ...

  7. Python Web Flask源码解读(三)——模板渲染过程

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  8. mybatis源码解读(四)——事务的配置

    上一篇博客我们介绍了mybatis中关于数据源的配置原理,本篇博客介绍mybatis的事务管理. 对于事务,我们是在mybatis-configuration.xml 文件中配置的: 关于解析 < ...

  9. MYSQL 源码解读系列 [线程池。。] ----dennis的博客

    http://blog.sina.com.cn/s/articlelist_1182000643_0_1.html

随机推荐

  1. Hello, Android 快速入门

    Hello, Android Android 开发与 Xamarin 简介 在这两节指南中,我们将 (使用 Xamarin Studio或 Visual Studio)建立我们的第一个 Xamarin ...

  2. 使用attrs.xml自定义属性

    控件有很多属性,如android:id.android:layout_width.android:layout_height等,但是这些属性都是系统自带的属性.使用attrs.xml文件,可以自己定义 ...

  3. Delphi Berlin 10.1 for 小米平板2 (Win 10) 电子罗盘测试

    Windows 10 下没有 Sensor.HeadingXSensor.HeadingYSensor.HeadingZ 需改用 Sensor.CompMagHeading

  4. Objective-c 动画

    想提高下以后做的应用给客户带去的体验,所以看了几天OC的CAAnimation动画类,也做了几个小案例,下面讲个别案例来做为本文的主要内容. 一:继承结构截图 上面截图中用得最多的类大概就是,CABa ...

  5. C#实现WebService服务 项目完整总结

    先说一下这个项目做了什么.先介绍一下背景(备注一下,每次项目发生更改之后,要进行clean 和rebuild两个操作,否则最新的更改保存不到exe文件中,这样上线后的系统还是执行得原有的已编译过的程序 ...

  6. 《Java4Android》视频学习笔记——包和访问权限(一)

    怎么打包?代码如下 package org.marsdroid; class Test{ public static void main(String args[]){ System.out.prin ...

  7. 【Effective Java】12、避免过度同步

    这里有一个辅助基础类 package cn.xf.cp.ch02.item16; import java.util.Collection; import java.util.Iterator; imp ...

  8. java8中的map和reduce

    java8中的map和reduce 标签: java8函数式mapreduce 2014-06-19 19:14 10330人阅读 评论(4) 收藏 举报  分类: java(47)  FP(2)  ...

  9. Java的对象初始化过程

    成员变量(字段)初始化顺序 在一个类里初始化的顺序是由成员变量在类里面的定义的顺序来决定的.即使成员变量大量散布于类的各个方法定义的中间,那些成员变量仍会在调用任何方法之前得以初始化,甚至在构造函数调 ...

  10. Number()、parseInt() 和 parseFloat() 的区别

    一:Number() 如果是Boolean值,true和false值将分别被转换为1和0. 如果是数字值,只是简单的传入和返回. 如果是null值,返回0. 如果是undefined,返回NaN. 如 ...