InnoDB---深入理解事务提交--02

三 为什么说InnoDB不符合WAL预写日志机制?

    在标题二中我们提出这样的一个调用栈:

innobase_commit_low(trx)

    -> trx_commit_for_mysql()

        -> trx_commit(trx)

            -> trx_commit_low()

                ->  trx_commit_in_memory()

                    -> lock_trx_release_locks()

    lock_trx_release_locks()这个函数中执行如下重要代码,所幸的是,这段代码中有段很重要的注释,帮助我们回答了本标题提出的问题。

/*********************************************************************//**

Releases a transaction's locks, and releases possible other transactions waiting because of these locks. Change the state of the transaction to TRX_STATE_COMMITTED_IN_MEMORY. */  //这段注释表明了lock_trx_release_locks()函数的功能

void

lock_trx_release_locks(

/*===================*/

    trx_t*    trx)    /*!< in/out: transaction */

{...

    /* The following assignment makes the transaction committed in memory  //这段注释很重要,需要重点理解

    and makes its changes to data visible to other transactions.  //在内存里提交后,本事务的数据即对其他事务可见

    NOTE that there is a small discrepancy from the strict formal //存在的一个问题:违反了WAL预写日志的机制

    visibility rules here: a human user of the database can see   //注释在说:即使违反了WAL预写日志的机制InnoDB也能保证正确性

    modifications made by another transaction T even before the necessary

    log segment has been flushed to the disk. If the database happens to

    crash before the flush, the user has seen modifications from T which  //在日志被刷出前,恰巧数据库引擎崩溃,而事务T被标识已经提交

    will never be a committed transaction. However, any transaction T2    //即使事务T2看到了事务T崩溃前且还没有刷出的数据,事务T2要想使

    which sees the modifications of the committing transaction T, and     //自己的修改生效,T2需要获取一个比事务TLSN更大的一个LSN

    which also itself makes modifications to the database, will get an lsn //当系统恢复的时候,事务T因为没有预写日志而被回滚,而事务T2也只能回滚(暗含之意是LSN会被用于识别并发事务的提交顺序)

    larger than the committing transaction T. In the case where the log    //刷出日志时需要使用LSN判断合法性

    flush fails, and T never gets committed, also T2 will never get

    committed. */

 

    /*--------------------------------------*/

    trx->state = TRX_STATE_COMMITTED_IN_MEMORY;  //此状态一旦设置,则本事务修改的数据则可以被其他事务所见(此时日志还没有被刷出到外存)

...

    lock_release(trx); //释放事务锁(事务状态已经被设置,表明提交已经完成,本事务的数据可以被其他事务所见到,所以可以释放锁,这就是SS2PL中提交点应该在何时设置的技术本质)

...

}

    这段代码的注释表明,InnoDB知道自己事务提交的规则“可能”不符合预写日志的规则也知道这样做带来的问题(可反复阅读上面的注释和解读),所以也提供了相应的解决问题的方式。解决方式如下面的代码调用栈:

innobase_commit()

{

    innobase_commit_low(trx)

    {

        -> trx_commit_for_mysql()

            -> trx_commit(trx)

                -> trx_commit_low()

                    ->  trx_commit_in_memory()

                        {

                        -> lock_trx_release_locks()

                           {

                               trx->state = TRX_STATE_COMMITTED_IN_MEMORY;  //内存中设置事务提交的标志,本事务的数据即刻被其他事务可见

                               ...  //省略一些代码

                               lock_release(trx);  //在设置事务提交已经完成的标志后才释放锁。锁在设置提交标志后才释放,符合SS2PL协议                       

                           }   

                           ...

                           lsn_t  lsn = mtr->commit_lsn();//获得最新的LSN               

                           if (lsn == 0) {

                               /* Nothing to be done. */

                           } else if (trx->flush_log_later) {

                               /* Do nothing yet */

                               trx->must_flush_log_later = true;

                           } else if (srv_flush_log_at_trx_commit == 0   //innodb_flush_log_at_trx_commit参数值为0,则不进行“预写日志”

                                      || thd_requested_durability(trx->mysql_thd)

                                          == HA_IGNORE_DURABILITY) {

                               /* Do nothing */

                           } else {   //否则,innodb_flush_log_at_trx_commit参数值为1,一定进行“预写日志”

                               trx_flush_log_if_needed(lsn, trx);  //第一次写日志的机会,此时写日志,则符合预写日志机制

                           }           

                           trx->commit_lsn = lsn;  //LSN赋值到事务结构体,待下面执行trx_commit_complete_for_mysql(trx)时再刷出日志                                    

                        }   

    }

...

    if (!read_only) {

        trx_commit_complete_for_mysql(trx);  //重要的步骤:刷出日志(刷出日志的过程可参见下一节日志落盘中的标题二)

        {    //第二次写日志的机会,此时写日志,则不符合预写日志机制

            trx_flush_log_if_needed(lsn, trx) -> trx_flush_log_if_needed_low() -> log_write_up_to(lsn, flush);

        }

    }

}

    从前面的分析可以看出,InnoDB是先设置了事务完成的状态,然后才刷出日志(第一次和第二次刷出日志的机会均在设置事务完成的状态之后),所以我们说InnoDB不符合预写日志机制。

    下面请看预写日志(WAL)的定义[1]

    In computer science, write-ahead logging (WAL) is a family of techniques for providing atomicity and durability (two of the ACID properties) in database systems.

    In a system using WAL, all modifications are written to a log before they are applied. Usually both redo and undo information is stored in the log.

    上面第二句话是说,在被修改的数据被应用之前日志要刷出,被修改的数据被应用即是数据被其他事务可见(注意不应理解为被修改的数据被刷出到外存),可见对应的就是事务状态被设置为已经提交。所以我们才说InnoDB不符合预写日志机制。



[1] 源自:https://en.wikipedia.org/wiki/Write-ahead_logging

猜你喜欢

转载自blog.csdn.net/fly2nn/article/details/61924840