zoukankan      html  css  js  c++  java
  • mysql源码解读之事务提交过程(二)

         上一篇文章我介绍了在关闭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:chuckmysqllog5-6-12mysql-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
  • 相关阅读:
    虚拟机VirtualBox 共享挂载问题:mount: /mnt/xxx: wrong fs type, bad option, bad superblock on xxx
    git 设置和取消代理
    (转载)数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知
    关于golang中IO相关的Buffer类浅析
    (转)golang获取当前时间、时间戳和时间字符串及它们之间的相互转换
    FFmpeg常用命令
    go cmd nohup 的坑
    Nginx配置详解(转)
    记录一次go性能调试的过程
    github徽标引入
  • 原文地址:https://www.cnblogs.com/cchust/p/3295547.html
Copyright © 2011-2022 走看看