代码改变世界

MySQL源码:innobase日志生成与管理

2012-08-01 20:08  竹 石  阅读(1545)  评论(0编辑  收藏  举报

 前言:很久没有写一些东西了,这次把一些以前写好的贴上来,可能其中有些不对或者不准确的地方请朋友指正,这里先谢谢大家了。2012-5-13 by whuai QQ:329570985 欢迎指正!  

Innodb存储引擎中的一个很重要的用来保证持久性的机制就是mini事务,在源码中用mtrMini-transaction)来表示,本书把它称做“物理事务”,这样叫是相对逻辑事务而言的,对于逻辑事务,做熟悉数据库的人都很清楚,它是数据库区别于文件系统的最重要特性之一,它具有四个特性ACID,用来保证数据库的完整性——要么都做修改,要么什么都没有做。物理事务从名字来看,是物理的,因为在innodb存储引擎中,只要是涉及到文件修改,文件读取等物理操作的,都离不开这个物理事务,可以说物理事务是内存与文件之间的一个桥梁。

前面已经介绍过innodb的页面缓冲区系统,已经知道在访问一个文件页面的时候,系统都会将要访问的页面载入到页面缓冲区中,然后才可以访问这个页面,此时可以读取或者更新这个页面,在再次将更新写入到文件中之前,这个页面都会处理缓冲区中。在这个过程中,有一个机制一直扮演着很重要的角色,那就是物理事务。

物理事务既然被称为事务,那它同样有事务的开始与提交,在innodb中,物理事务的开始其实就是对物理事务的结构体mtr_struct的初始化,其中包括下面一些成员:

struct mtr_struct{

       dyn_array_t   memo;    /*!< memo stack for locks etc. */

       dyn_array_t   log;  /*!< mini-transaction log */

       ulint        n_log_recs; /* count of how many page initial log records

                            have been written to the mtr log */

       ulint        log_mode;

       ib_uint64_t    start_lsn;/* start lsn of the possible log entry for this mtr */

       ib_uint64_t    end_lsn;/* end lsn of the possible log entry for this mtr */

};

这个结构体中前面两个成员是两个动态数组,第一个成员memo用来存储所有这个物理事务用到(访问)的页面,这些页面都是被所属的物理事务上了锁的(读锁或者写锁,某些时候会不上锁);成员log用来存储这个物理事务在访问修改数据页面的过程中产生的所有日志,这个日志就是数据库中经常说到的重做(redo)日志。成员n_log_recs表示这个物理事务产生的日志量;log_mode表示这个物理事务的日志模式,包括MTR_LOG_ALL(写日志)、MTR_LOG_NONE(不写日志)等;最后两个成员表示这个物理事务开始前的LSN及这个物理事务提交后产生的新的LSN

首先在系统将一个页面载入到缓冲区的时候,需要新开始一个(mtr_start)或者一个已经开始的物理事务,载入时需要指定页面的获取方式,比如是用来读取的还是用来修改的,这样会影响物理事务对这个页面的上锁情况,如果用来修改,则上X锁,否则上S锁(当然还可以指定不上锁)。在确定了获取方式、这个页面的表空间ID及页面号之后,就可以通过函数buf_page_get来获取指定页面了,当找到相应页面后,物理事务就要对它上指定的锁,此时需要对这个页面的上锁情况进行检查,一个页面的上锁情况是在结构体buf_block_struct中的lock中体现的,此时如果这个页面还没有上锁,则这个物理事务直接对其上锁,否则还需要考虑两个锁的兼容性,只有两个锁都是共享锁(S)的情况下才是可以上锁成功的,否则需要等待。当上锁成功后,物理事务会将这个页面的内存结构存储到上面提到的memo动态数组中。然后这个物理事务就可以访问这个页面了。

物理事务对页面的访问包括两种操作,一种是读,另一种是写,读就是简单读取其指定页面内偏移及长度的数据;写则是指定从某一偏移开始写入指定长度的新数据,同时如果这个物理事务是写日志的(MTR_LOG_ALL),此时还需要对刚才的写操作记下日志,这里的日志就逻辑事务中提到的REDO日志。写下相应的日志之后,同样将其存储到上面的log动态数组中,同时要将上面结构体中的n_log_recs自增,维护这个物理事务的日志计数值。

物理事务的读写过程主要就是上面介绍的内容,其最重要的是它的提交过程。物理事务的提交是通过mtr_commit来实现的,物理事务的提交主要是将所有这个物理事务产生的日志写入到innodb的日志系统的日志缓冲区中,然后等待srv_master_thread线程定时将日志系统的日志缓冲区中的日志数据刷到日志文件中,这一部分会单独在其它章节点讲述。

上面已经讲过,物理事务和逻辑事务一样,也是可以保证数据库操作的完整性的,一般说来,一个操作必须要在一个物理事务中完成,也就是指要么这个操作已经完成,要么什么也没有做,否则有可能造成数据不完整的问题,因为在数据库系统做REDO操作时是以一个物理事务为单位做的,如果一个物理事务的日志是不完整的,则它对应的所有日志都不会重做。那么如何辨别一个物理事务是否完整呢?这个问题是在物理事务提交时用了个很巧妙的方法保证了,在提交前,如果发现这个物理事务有日志,则在日志最后再写一些特殊的日志,这些特殊的日志就是一个物理事务结束的标志,那么提交时一起将这些特殊的日志写入,在重做时如果当前这一批日志信息最后面存在这个标志,则说明这些日志是完整的,否则就是不完整的,则不会重做。

物理事务提交时还有一项很重要的工作就是处理上面结构体中动态数组memo中的内容,现在都已经知道这个数组中存储的是这个物理事务所有访问过的页面,并且都已经上了锁,那么在它提交时,如果发现这些页面中有已经被修改过的,则这些页面就成为了脏页,这些脏页需要被加入到innodbbuffer缓冲区中的更新链表中(讲BUFFER时已经讲过),当然如果已经在更新链中,则直接跳过(不能重复加入),svr_master_thread线程会定时检查这个链表,将一定数目的脏页刷到磁盘中,加入之后还需要将这个页面上的锁释放掉,表示这个页面已经处理完成;如果页面没有被修改,或者只是用来读取数据的,则只需要直接将其共享锁(S锁)释放掉即可。

上面的内容就是物理事务的一个完整的讲述,因为它是比较底层的一个模块,牵扯的东西比较多,这里重点讲述了物理事务的意义、操作原理、与BUFFER系统的关联、日志的产生等内容。