MySQLInnoDB特性:两次写(特性:两次写(DoubleWrite))
一、经典Partial page write问题?
介绍double write之前我们有必要了解partial page write(部分页失效)问题。
InnoDB的Page Size一般是16KB,其数据校验也是针对这16KB来计算的,将数据写入到磁盘是以Page为单位进行操作的。
我们知道,由于文件系统对一次大数据页(例如InnoDB的16KB)大多数情况下不是原子操作,这意味着如果服务器宕机了,
可能只做了部分写入。16K的数据,写入4K时,发生了系统断电/os crash ,只有一部分写是成功的,这种情况下就是partial
page write问题。
有经验的DBA可能会想到,如果发生写失效,MySQL可以根据redo log进行恢复。这是一个办法,但是必须清楚地认识
到,redo log中记录的是对页的物理修改,如偏移量800,写’aaaa’记录。如果这个页本身已经发生了损坏,再对其进行重做是
没有意义的。MySQL在恢复的过程中检查page的checksum,checksum就是检查page的最后事务号,发生partial page write
问题时,page已经损坏,找不到该page中的事务号。在InnoDB看来,这样的数据页是无法通过checksum验证的,就无法恢
复。即时我们强制让其通过验证,也无法从崩溃中恢复,因为当前InnoDB存在的一些日志类型,有些是逻辑操作,并不能做
到幂等。
为了解决这个问题,InnoDB实现了double write buffer,简单来说,就是在写数据页之前,先把这个数据页写到一块独立的物
理文件位置(ibdata),然后再写到数据页。这样在宕机重启时,如果出现数据页损坏,那么在应用redo log之前,需要通过
该页的副本来还原该页,然后再进行redo log重做,这就是double write。double write技术带给innodb存储引擎的是数据页的
可靠性。
二、double write体系结构及工作流程?
double write由两部分组成,一部分是InnoDB内存中的double write buffer,大小为2M,另一部分是物理磁盘上ibdata系统表空
间中大小为2MB,共128个连续的Page,既2个分区。其中120个用于批量写脏,另外8个用于Single Page Flush。做区分的原
因是批量刷脏是后台线程做的,不影响前台线程。而Single page flush是用户线程发起的,需要尽快的刷脏并替换出一个空闲
页出来。
对于批量刷脏,每次找到一个可做flush的page,对其持有S lock,然后将该page拷贝到dblwr中,当dblwr满后者一次批量刷
脏结束时,将dblwr中的page全部刷到ibdata中,注意这是同步写操作;然后再唤醒后台IO线程去写数据页。当后台IO线程完
成写操作后,会去更新dblwr中的计数以腾出空间,释放block上的S锁,完成写入。
对于Single Page Flush,则做的是同步写操作,在挑出一个可以刷脏的page后,先加入到dblwr中,刷到ibdata,然后写到用
户表空间,完成后,会对该用户表空间做一次fsync操作。
Single Page Flush在buffer pool中free page不够时触发,通常由前台线程发起,由于每次single page flush都会导致一次fsync
操作,在大并发负载下,如果大量线程去做flush,很显然会产生严重的性能下降。Percona在5.6版本中做了优化,可以选择
由后台线程lru manager来做预刷,避免用户线程陷入其中。
如果发生了极端情况(断电),InnoDB再次启动后,发现了一个Page数据已经损坏,那么此时就可以从double write buffer中
进行数据恢复了。
double write工作流程如下:
当一系列机制(main函数触发、checkpoint等)触发数据缓冲池中的脏页进行刷新到data file的时候,并不直接写磁盘,而是
会通过memcpy函数将脏页先复制到内存中的double write buffer,之后通过double write buffer再分两次、每次1MB顺序写入
共享表空间的物理磁盘上。然后马上调用fsync函数,同步脏页进磁盘上。由于在这个过程中,double write页的存储时连续
的,因此写入磁盘为顺序写,性能很高;完成double write后,再将脏页写入实际的各个表空间文件,这时写入就是离散的
了。各模块协作情况如下图(第一步应为脏页产生的redo记录log buffer,然后log buffer写入redo log file,为简化次要步骤直
接连线表示):