在《MySQL技术内幕——InnoDB存储引擎(第2版)》中有对doublewrite(两次写)的如下描述:
如果说Insert Buffer带给InnoDB存储引擎的是性能上的提升,那么doublewrite(两次写)带给InnoDB存储引擎的是数据页的可靠性。
当发生数据库宕机时,可能InnoDB存储引擎正在写入某个页到表中,而这个页只写了一部分,比如16KB的页,只写了前4KB,之后就发生了宕机,这种情况被称为部分写失效(partial page write)。在InnoDB存储引擎未使用doublewrite技术前,曾经出现过因为部分写失效而导致数据丢失的情况。
有经验的DBA也许会想,如果发生写失效,可以通过重做日志进行恢复。这是一个办法。但是必须清楚地认识到,重做日志中记录的是对页的物理操作,如偏移量800,写'aaaa'记录。如果这个页本身已经发生了损坏,再对其进行重做是没有意义的。这就是说,在应用(apply)重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是doublewrite。
针对这段话,我有些疑问,如果说重做日志记录的是对页的物理操作,那么就应该是可以多次重写而不会有问题的。既然可以多次重写,为何还需要先还原页,再重写呢?
带着这个问题,我查阅资料搞明白了其中的缘由,特此记录如下。如有错误还请批评指教。
准确的说,重做日志是物理逻辑(physiological)日志,说它是物理的,是因为要重做的页码从物理上标识出来,而逻辑则是指它记录的页内操作是逻辑的。
比如日志格式是
<space id, page no, operation code, data>
记录内容是
<0, 2600, insert, after record at offset 8192>
这样的设计平衡了重做日志的数据量和重做日志在页上操作的幂等性(不需要记录更改之后的物理页整个的数据是怎么样的)。但是物理逻辑日志带来的问题是不能对一个部分修改的页做重做,它必须要先把页恢复到一个一致的状态,然后再对页重放Redo Log。
另外,InnoDB是怎么知道页损坏的?每个页在末尾都有校验值(Checksum)。校验值是最后写到页上的内容,所以如果页的内容跟校验值不匹配,说明这个页是损坏的。
参考文献:
- 数据库系统概论(第六版)》中文版,P419,ARIES算法
- percona官网文章:https://www.percona.com/blog/how-innodb-handles-redo-logging/
- percona官网文章:https://www.percona.com/blog/innodb-double-write/
- 《高性能MySQL(第三版)》中文版,P360,双写缓冲
- 数据库内核月报