回顾
上个章节我们深入浅出了MySQL的Insert Buffer和新版本的InnoDB中支持的Change Buffer,从实现原理层面也做了一些探索。由此,我们可以给出结论,对于非聚簇非唯一的索引,通过Change Buffer可以让MySQL的DML操作更快,但是同时,也要注意Change Buffer膨胀导致缓冲池空间紧张的问题。OK,如果说Change Buffer给MySQL的DML操作带来了性能上的提升,那么今天我们来说一说InnoDB的另一个关键特性——doublewrite(两次写)。
为什么需要两次写?
数据库宕机导致数据丢失
如果运行中的MySQL数据库突然发生宕机,此时InnoDB存储引擎正在将缓冲池中的页持久化到磁盘中,但是这个时刻数据库宕机了,那么会导致部分数据刷到了磁盘中,部分数据还在内存中,这种情况在MySQL中称为partial page write
(部分写失效)。
redo log无法对页损坏做恢复
redo log记录的是对页的物理操作,例如:偏移量800,写入'xxx'记录,但是页本身如果是损坏的,那么redo也是无济于事的。
doublewrite是怎么做的?
InnoDB在应用(apply)redo log前,写入了页的副本,当写入失效时,先通过副本还原该页,再进行redo。
doublewrite结构
内存中的doublewrite buffer
大小为2MB
物理磁盘上共享表空间中连续的128个页,即两个区(extent),大小为2MB。
image.png
doublewrite工作原理
在对缓冲池的脏页进行刷新时,并不直接写入磁盘,而是会通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后通过doublewrite buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题。在这个过程中,因为doublewrite页是连续的,所以写入double write是顺序写的,开销并不是很大。等数据进入doublewrite的页后,再离散地将数据写入各个表空间文件中。
查看doublewrite的运行情况
> SHOW GLOBAL STATUS LIKE 'innodb_dblwr%'\G;
Innodb_dblwr_pages_written
doublewrite写入缓存的页数
Innodb_dblwr_writes
实际写入磁盘的次数
系统在高峰时的Innodb_dblwr_pages_written:Innodb_dblwr_writes总体符合64:1,这对系统的IO压力并不是很高。
通俗地理解doublewrite
假设你要对一个表执行一段时间较长的SQL,你害怕在这段时间内MySQL断电了导致你的数据变脏了,虽然记录了SQL,但是你需要回到未执行这条SQL前的状态。那么你需要复制出一个一样的表,如果你的SQL失败,那么从copy的表中重新执行这条SQL。笔者认为,这便是doublewrite的思想。
禁用doublewrite
skip_innodb_doublewrite
通过这个参数,你可以禁用doublewrite。但是不建议这么做,任何时候,用户都应该开启doublewrite来保证你的MySQL具备容灾性。