1. 页断裂
1.1 什么叫做页断裂(partial write)
页断裂是数据库宕机时,数据库页面只有部分写入磁盘,导致页面出现不一致的情况。
1.2 为什么会发生页断裂
InnoDB中有记录(Row)被更新时,先将其在Buffer Pool中的page更新,并将这次更新记录到Redo Log file中,这时候Buffer Pool中的该page就是被标记为Dirty(脏页)。在适当的时候(Buffer Pool不够、Redo不够,系统闲置等),这些Dirty Page会被Checkpoint刷新到磁盘进行持久化操作。
mysql的一个update需要经历什么最终持久化到磁盘?
我们知道,InnoDB的Page Size是16KB,其数据校验也是针对这16KB来计算的,将数据写入到磁盘是以Page为单位进行操作的。我们知道文件系统是以4k为单位写入,机械磁盘是以扇区(512字节)为单位写入(SSD本质上虽然没有扇区概念,但为了兼容机械盘,也搞出了这么一个512字节扇区这么一个写方式),不能保证MySQL数据页面16KB的一次性原子写。试想,在某个Dirty Page flush的过程中,发生了系统断电(或者OS崩溃),16K的数据只有8K被写到磁盘上,只有一部分写是成功的,这种现象被称为(partial page writes、torn pages、fractured writes)。
查看innoDB的Page Size大小
mysql> show variables like 'innodb_page_size';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.00 sec)
查看OS block Size大小
$ getconf PAGESIZE
4096
1.3 InnoDB使用redo log可以恢复页断裂吗
一旦partial page writes发生,那么在InnoDB恢复时就很尴尬:redo log的页大小一般设计为512个字节,因此redo log page本身不会发生break page。用redo log来解决partial write 理论上是可行的,不过innodb的redo log是物理逻辑日志,并不是纯物理日志,因此发生partial write后崩溃恢复过程中不能直接应用redo log ,innodb发现break page后实际上会报错。
注:mysql的逻辑日志、物理日志与物理逻辑日志
物理逻辑日志不是完全幂等的,这取决于重做日志类型,对于INSERT产生的日志其不是幂等的。
2. double write
2.1 简述
为了解决页断裂(partial write)问题,InnoDB实现了double write机制。
简单来说,在写数据页之前,先把这个数据页写到一个独立的物理文件位置(ibdata),然后再写到数据页。这样在宕机恢复的时候,如果数据页损坏,那么在应用redo log之前,需要通过该页的副本来还原该页,然后在进行redo log重做。这就是doublewrite。doublewrite技术带给innodb存储引擎的是数据页的可靠性。
2.2 double write体系结构和工作流程
double write由两部分组成,一部分是InnoDB内存中的double write buffer,大小为2MB,另一部分是物理磁盘上的ibdata,系统表空间中大小为2MB,共128个连续的Page(2*1024/16KB=128),即两个分区(extend)一个段(segment)。其中120个页用于批量刷新脏页(如LRU LIST刷新与FLUSH LIST刷新这两种刷新策略),另外8个页用于单页刷新(Single Page Flush)。做区分的原因是批量刷脏是后台线程做的,不影响前台线程。而单页刷新是用户线程发起的,需要尽快的刷脏页并替换出一个空闲页出来。
InnoDB刷新(写出)缓冲区中的数据页时采用的是一次写多个页的方式:
- 多个页就可以先顺序写入到double write buffer,并调用fsync()保证这些数据被刷新到double write磁盘(ibdata)。
- 然后数据页调用fsync()被刷新到实际存储位置;
- 故障恢复时InnoDB检查double write Buffer与数据页原存储位置的内容,若double write页处于页断裂状态,则简单的丢弃;若数据页不一致,则从double write页还原。
由于double write页落盘与数据页落盘在不同的时间点,不会出现double write页和数据页同时发生断裂的情况,因此doublewrite技术可以解决页断裂问题,进而保证了重做日志能顺利进行,数据库能恢复到一致的状态。
2.3 double write性能损耗
表面看上去,每个页面都写了2遍,会非常影响性能。但实际上,由于所写的页面会先缓存到内存中,因此每一部分缓存空间在满了之后才会真正的写入文件。并且double write是一个连续的存储空间,所以硬盘在写数据的时候是顺序写,而不是随机写,这样性能很高。doublewrite有效利用这个特点,所以降低并不会相差1倍,经过测试,大概5-10%左右。当然,这是针对普通磁盘。对于目前比较流行的SSD来说,随机写已经不是问题,性能影响可能更小。
2.4 double write的配置
double write并不是什么特性或优点,它只是一个被动解决方案而已。这个问题的本质就是磁盘在写入时,都是以512字节为单位,不能保证MySQL数据页面16KB的一次性原子写,所以才有可能产生页面断裂的问题。而目前有些厂商从硬件驱动层面做了优化,可以保证16KB(或其他配置)数据的原子性写入。如果真是这样,那么两次写就完全没有必要了,取消两次写,才是最终级优化。
mysql的double write默认开启,参数skip_innodb_doublewrite虽然可以禁止使用doublewrite功能,但还是强烈建议大家使用doublewrite,避免部分写失效问题。
mysql> show variables like '%double%';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| innodb_doublewrite | ON |
+--------------------+-------+
1 row in set (0.00 sec)
2.5 InnoDB能否通过其他方式解决partial write
其实保证mysql数据页面(16KB)原子性的写入到磁盘(每次512字节)中,就可以解决partial write。
- 如果系统表空间文件(“ibdata文件”)位于支持原子写入的Fusion-io设备上,就能避免partial write ;
- 阿里云polardb,在底层分布式文件系统PolarFS能提供页大小(如16)KB小的原子写入,无需double write机制来避免partial write。
推荐阅读
MySQL InnoDB特性:两次写(DoubleWrite)
页断裂(partial write)与doublewrite技术