double write(两次写)技术的引入是为了提高数据写入的可靠性。这里先说明一下page页坏的问题。因为数据库中一个page的大小是16KB,数据库往存储上写数据是以更小的单位进行的,这就产生了一个问题:当发生数据库宕机时,可能InnoDB存储引擎正在写入某个页到表中,而这个页只写了一部分,比如16KB的页,只写了前4KB,之后就发生了宕机,这种情况被称为部分写失效(partial page write)。在InnoDB存储引擎未使用double write技术前,曾经出现过因为部分写失效而导致数据丢失的情况。
double write的原理是:每次写入一个page时,先把page写到double write buffer中。如果在写double write buffer时发生了意外,但是数据文件中原来的page不受影响,这样在下次启动时可以通过InnoDB的redo log进行恢复。在写double write buffer成功后,MySQL会把double write buffer的内容写到数据文件中,如果在这个过程又出现了意外,没有关系,重启后MySQL可以从double write buffer找到好的page,再用好的page去覆盖磁盘上坏的page,解决page坏的问题。这就是double write,说白了就是一种备份镜像的思想。
如图2-7所示,double write默认存放在ibdata1共享表空间里,默认大小为2MB,写之前将脏页写入到innodb buffer中的double write buffer(2MB)中,将2MB的buffer数据直接写入共享表空间ibdata1的double write段中。若写共享表空间的double write失败了,没有关系,因为此时的数据文件ibd中的数据是完整干净的,处于一致的状态,可以通过redo log进行恢复;如果是写到ibd文件时发生了宕机,此刻在原来的ibdata1中存在副本,可以直接覆盖到ibd文件(对应的页)中去,然后用redo log进行恢复。
图2-7
肯定有读者要问,MySQL redo log不是已经记录了所有的数据历史记录了吗?
要弄明白这个问题,首先要了解一下MySQL redo log里面记录的是什么东西。日志分为物理日志和逻辑日志。物理日志就是直接记录数据、记录被修改的页的偏移量,优点就是不依赖原页面的内容,用日志的内容可以直接覆盖到磁盘上面,缺点是占用的空间太多。逻辑日志的优点是比较简洁,而且占用的空间要小,缺点是需要依赖原page内容,而且会有部分执行和操作一致性的问题。
所以说MySQL redo log是物理逻辑的,它将物理日志和逻辑日志相结合,取其利,避其害,从而达到一个更好的状态,具体说明就是:物理表示记录的日志针对的是页(page)的修改,为每个页上的操作单独记日志;逻辑表示记录日志的内容是逻辑的,比如物理上来说要修改Page Header页头的内容、要修改相邻记录里的链表指针等。这些本是一些物理操作,而InnoDB为了节省日志量,设计为逻辑处理的方式。这样的一个MySQL redo log其实仍然没有解决数据的一致性问题。如果在redo log应用到磁盘时,在写一个Page到磁盘时发生了故障,可能导致Page Header的记录数被加1(表示此Page已恢复完成),但是页内的逻辑日志发生了故障,这时数据就不一致了。MySQL用double write方法来解决此问题。
可能有人会问double write对性能影响大吗?如果页大小是16KB,那么就有128个页(1MB)需要写,但是128个页写入到共享表空间是1次I/O完成的,也就是说double write写开销是1+128次。其中,128次是写数据文件表空间。在传统的机械式硬盘中,double write buffer写入是顺序操作。相对于数据文件写入这样的随机写操作来说,顺序写入的代价还是小的。在新型的SSD存储中,double write buffer导致数据重复写入对于SSD寿命有较大影响。如果SSD设备支持原子写,那么在MySQL中可以通过设置参数innodb_doublewrite=0关掉double write的功能。
查看double write的工作情况,如图2-8所示。执行命令“show global status like 'innodb_dblwr%'\G”,可以观察到double write运行的情况。这里double write一共写了18445页,但实际的写入次数为434。如果发现你的系统在高峰时Innodb_dblwr_pages_written:Innodb_dblwr_writes远小于64:1,那么说明你的系统写入压力并不是很高。
图2-8
其实两次写并不是什么特性或优点,只是一个被动解决方案而已。这个问题的本质就是磁盘在写入时不能保证MySQL数据页面16KB的一次性原子写,所以才有可能产生页面断裂的问题。有些厂商从硬件驱动层面做了优化,可以保证16KB(或其他配置)数据的原子性写入,那么两次写就完全没有必要了。在以前的一个项目中,Fusion-I/O的SSD卡是支持原子写的技术,不需要开启两次写即可提升MySQL数据写入延迟,同时延长SSD存储寿命。