mysql源码:关于innodb中两次写的探索

两次写能够说是在Innodb中很独特的一个功能点,而关于它的说明或者解释很是少,至于它存在的缘由更没有多少文章来讲,因此我打算专门对它作一次说明。数据库

首先说明一下为何会有两次写这个东西:
由于innodb中的日志是逻辑的,所谓逻辑就是好比当插入一条记录时,它可能会致使在某一个页面(这条记录最终被插入的位置)的多个偏移位置写入某个长度的值,好比页头的记录数,槽数,页尾槽数据,页中的记录值等等,这些本是一些物理操做,而innodb为了节约日志量及其它一些缘由,设计为逻辑处理的方式,那就是它会在一个页面的基础上,把一条记录插入,那么在日志记录中记录的内容为表空间号、页面号、记录的各个列的值等等,在内部转换为上面的物理操做。数组

但这里的一个问题是,若是那个页面自己是错误的,这种错误有多是由于写断裂(1个页面为16K,分屡次写入,后面的有可能没有写成功,致使这个页面不完整)引发的,那么这个逻辑操做就没办法完成了,由于它的前提是这个页面仍是正确的,完整的,由于若是这个页面不正确的话,这个页面里的数据是无效的,有可能产生各类不可预料的问题。缓存

那么正是由于这个问题,因此必需要首先保证这个页面是正确的,方法就是两次写,它的思想最终是一种备份思想,也就是一种镜像。性能

下面就它的实现方式说明一下:测试

首先是它的建立,在初始化一个库的时候,系统会在系统页面5号页面的尾部(大约是16K-200字节的位置)初始化全部关于两次写的信息,这些信息包括:spa

----------------------------------------------------------------
#define TRX_SYS_DOUBLEWRITE_FSEG 0 /*这里存储的是两次写页面所在的段的地址信息 */
#define TRX_SYS_DOUBLEWRITE_MAGIC FSEG_HEADER_SIZE
/*!< 用来判断是否是已经初始化过两次写页面 */
#define TRX_SYS_DOUBLEWRITE_BLOCK1 (4 + FSEG_HEADER_SIZE)
/*!两次写页面的第一个簇的首地址,两次写页面总共有2个簇,一个簇为64个页面*/
#define TRX_SYS_DOUBLEWRITE_BLOCK2 (8 + FSEG_HEADER_SIZE)
/*!第二个簇的首地址*/
#define TRX_SYS_DOUBLEWRITE_REPEAT 12 /*!< 将上面的MAGCI、BLOCK1与BLOCK2重复存储,防止本身的不完整*/
----------------------------------------------------------------设计

两次写总共包括2M(默认值)的数据,有2个BLOCK,那么每个BLOCK是1M,每一个页面是16K,那么一个BLOCK包括64个页面,正是一个簇的大小。,因此其实两次写页面的空间是2个簇的空间。日志

那么初始化所要作的工做就是将上面的信息补充完整,BLOCK1与BLOCK2分别对应2个簇的首地址,同时还要申请2个簇的内存空间,用来缓存这些数据。事务

除上面的信息以外,还会有一个空间用来存储这128个页面的页面信息,是用来在刷两次写页面以后,要作对应的页面刷盘操做,这是一个长度为128的数组。内存

有了上面的信息以后,则两次写初始化完成。

下面说明一下它的使用过程:
在作页面刷盘的时候,若是开启了两次写的功能,则innodb要作的不是简单的直接将数据作io操做写入到硬盘,而是先将当前要写的页面按照顺序拷贝到两次写内存缓存空间中去,上面已经说了它的大小为128个页面的大小,同时要在页面数组中对应的将页面的地址记录下来,而后就算刷盘完成了,但实际上,此时要写出的页面都在两次写的内存缓存空间中的。

当缓存空间满了的时候,上面的操做会触发真正的刷盘操做,两次写的写入是,首先判断当前缓存中有多少个簇,也就是说判断BLOCK1中有没有数据,若是没有数据则直接不写了,若是有则写入,以这个簇实际大小写入,而后再判断BLOCK2中有没有数据,而后作一样的处理。
在写完两次写缓存中的数据以后,而后再将页面数组中的每个页面按照顺序从前到后再一个一个的将其刷入到文件中,此时,才真正的将这些页面刷盘完成。

固然两次写缓存写出硬盘不仅是上面一个机会,其它刷盘的操做也会触发这个操做,那时可能缓存中的页面数尚未达到128个。

上面已经说完了两次写的写入及初始化,最后说一下它是如何起做用的:

在数据库启动时(异常关闭的状况下),都会作数据库恢复(redo)操做,恢复的过程当中,数据库都会检查页面是否是合法(校验等等),若是发现一个页面校验结果不一致,则此时会用到两次写这个功能,这个特色也正是为了处理这样的错误而设计的。

此时的操做很明白了,将两次写的2个BLOCK(簇)都读出来,而后将全部这些页面写回到对应的页面中去,那么这时能够保证这些页面是正确的,而且是在写入前已经更新过的(最新数据)。
在写回对应页面中去以后,那么就能够在这基础上继续作数据库恢复了,以后则不会再遇到这样的问题了,由于已经将最后有可能产生写断裂的数据页面都恢复了。

问题:
上面说的都是数据页面有问题的状况下能够经过两次写页面来恢复,可是若是2次写页面自己发生写断裂怎么办?
对于这个问题,实际上是不用担忧的,由于若是两次写有问题,则自己数据页面就没有作写操做,此时系统挂了,发生错误的是两次写页面,而数据页面在挂以前都是在buffer里面,文件中仍是当前事务操做前的值,它本身没有变,仍是一致状态,因此两次写页面压根就不会被使用到。

总结:

1. 两次写在任什么时候候记录的都是数据库最后发生改变的若干页面(最多128个),在数据库不断工做的过程当中,它会不断的被覆盖,它始终是最新的数据,记录的是修改以后的页面数据,而不是修改以前的数据,它的做用不是还原数据,而是保证不会丢失修改。 2. 至于性能问题,表面看上去,它是每个页面都写了2遍,则会很是影响性能,但实际上,因为将所写的页面都先缓存到内存中,到达128个以后才真正写入,那么对于磁盘而言,连续写与分散写(每一个页面本身写)的性能相差很大的,而两次写正是将一个簇数量的页面组合起来造成2个连续的空间写入到两次写空间中,有效的利用这了这特色,因此性能是不会相差1倍的。实际上通过测试,可能两次写使得性能下降了10%。 3. 有其它一些数据库彻底没有相似2次写的问题,好比达梦等,这个主要是因为它们采用了全物理的REDO,将一个页面的写操做都拆成一个个的小的物理写入,这种状况下就不会存在写断裂的状况,由于无论怎么写,日志都是对一个页面操做的重演,在REDO作完以后,页面的状态确定是正确的。

相关文章
相关标签/搜索