双写缓冲区**/**双写机制是 InnoDB 的三大特性之一,还有两个是 Buffer Pool、自适应 **Hash **索引。
它是一种特殊文件 flush 技术,带给 InnoDB 存储引擎的是数据页的可靠性。它的作用是,在把页写到数据文件之前,InnoDB 先把它们写到一个叫 doublewrite buffer(双写缓冲区)的连续区域内,在写 doublewrite buffer 完成后,InnoDB 才会把页写到数据文件的适当的位置。如果在写页的过程中发生意外崩溃,InnoDB 在稍后的恢复过程中在 doublewrite buffer 中找到完好的 page 副本用于恢复。
所以,虽然叫双写缓冲区,但是这个缓冲区不仅在内存中有,更多的是属于MySQL 的系统表空间,属于磁盘文件的一部分。那为什么要引入一个双写机制呢?
InnoDB 的页大小一般是 **16KB,其数据校验也是针对这 16KB 来计算的,将数据写入到磁盘是以页为单位进行操作的。而操作系统写文件是以 4KB 作为单位**的,那么每写一个 InnoDB 的页到磁盘上,操作系统需要写 4 个块。
而计算机硬件和操作系统,在极端情况下(比如断电)往往并不能保证这一操作的原子性,16K 的数据,写入 4K 时,发生了系统断电或系统崩溃,只有一部分写是成功的,这种情况下会产生 partial page write(部分页写入)问题。这时页数据出现不一样的情形,从而形成一个”断裂”的页,使数据产生混乱。在InnoDB 存储引擎未使用 doublewrite 技术前,曾经出现过因为部分写失效而导致数据丢失的情况。
doublewrite buffer 是 InnoDB 在表空间上的 128 个页(2 个区,extend1 和extend2),大小是 2MB。为了解决部分页写入问题,当 MySQL 将脏数据 flush 到数据文件的时候, 先使用 memcopy 将脏数据复制到内存中的一个区域(也是2M),之后通过这个内存区域再分 2 次,每次写入 1MB 到系统表空间,然后马上调用 fsync 函数,同步到磁盘上。在这个过程中是顺序写,开销并不大,在完成 doublewrite 写入后,再将数据写入各数据文件文件,这时是离散写入。
所以在正常的情况下**, MySQL 写数据页时,会写两遍到磁盘上,第一遍是写到 doublewrite buffer,第二遍是写到真正的数据文件中。如果发生了极端情况(断电),InnoDB 再次启动后,发现了一个页数据已经损坏,那么此时就可以从doublewrite buffer 中进行数据恢复了。
前面说过,位于系统表空间上的 doublewrite buffer 实际上也是一个文件, 写系统表空间会导致系统有更多的 fsync 操作, 而硬盘的 fsync 性能因素会降低MySQL 的整体性能。不过在存储上,doublewrite 是在一个连续的存储空间, 所以硬盘在写数据的时候是顺序写,而不是随机写,这样性能影响不大,相比不双写, 降低了大概 5-10%左右。
所以,在一些情况下可以关闭 doublewrite 以获取更高的性能。比如在 slave 上可以关闭,因为即使出现了 partial page write 问题,数据还是可以从中继日志中恢复。比如某些文件系统 ZFS 本身有些文件系统本身就提供了部分写失效的防范机制,也可以关闭。
在数据库异常关闭的情况下启动时,都会做数据库恢复(redo)**操作,恢复的过程中,数据库都会检查页面是不是合法(校验等等),如果发现一个页面校验结果不一致,则此时会用到双写这个功能。
有经验的同学也许会想到,如果发生写失效,可以通过重做日志(Redo Log) 进行恢复啊!但是要注意,重做日志中记录的是对页的物理操作(是修改日志,不是全量数据记录),如偏移量 800, 写’ aaaa’记录,而不是页面的全量记录,而如果发生 partial page write(部分页写入,发生partial page write问题时,page已经损坏,找不到该page中的事务号,就无法恢复)问题时,出现问题的是未修改过的数据,此时重做日志(Redo Log)无能为力。
如果是写 doublewrite buffer 本身失败,那么这些数据不会被写到磁盘, InnoDB 此时会从磁盘载入原始的数据,然后通过 InnoDB 的事务日志来计算出正确的数据,重新写入到 doublewrite buffer,这个速度就比较慢了。如果 doublewrite buffer 写成功的话,但是写数据文件失败,innodb 就不用通过事务日志来计算了, 而是直接用 doublewrite buffer 的数据再写一遍,速度上会快很多。
总体来说,doublewrite buffer 的作用有两个: 提高 innodb 把缓存的数据写到硬盘这个过程的安全性;间接的好处就是,innodb 的事务日志不需要包含所有数据的前后映像,而是二进制变化量,这可以节省大量的 IO。