考虑到一个问题,Mysql为何需要Double write来双写数据到数据块?redo log不是已经记录了所有的数据历史记录了吗?

要弄明白这个问题,首先先要了解一下mysql redo log里面记录的是什么?

日志分为物理日志和逻辑日志,物理日志就是直接记录数据/数据,记录被修改的page的偏移量,优点就是不依赖原page的内容,用日志的内容可以直接覆盖到磁盘上面,而缺点就是占用的空间太多,比如新增一个btree索引或者一个update操作。逻辑日志只是在关系表上面的元操作,比如update一行数据,delete一行数据等,优点就是比较简洁而且占用的空间要小,缺点就是需要依赖原page内容,而且会有部分执行和操作一致性的问题。

mysql 的redo log将物理日志和逻辑日志相结束 ,取其利,避其害,从而达到一个更好的状态。具体为:

 A. 物理到page. 将操作细分到页级别。为每个页上的操作单独记日志。
     比如,一个Insert分别在2个B-Tree的节点上做了插入操作,那么就分别为每一个页的操作记录一条日志。
  B. Page内采用逻辑的日志。比如对一个B-Tree的页内插入一条记录时,物理上来说要修改Page Header的 内容(如,页内的记录数要加1),要插入一行数据到某个位置,要修改相邻记录里的链表指针,要修改Slot的属性等。从逻辑上来说,就是在这个页内插入了一行记录。因此Page内的逻辑日志只记录:’这是一个  插入操作’和’这行数据的内容‘。

这样的一个redo log取长补短,难道就没有什么问题了吗?其实数据的一致性问题仍然没有解决,如果在redo log应用到磁盘 时,在写一个Page到磁盘时发生了故障,可能导致Page Header的记录数被加1了(表示已此page已恢复完成),但是页内的逻辑日志在发生了故障,这时数据就不一致了。好在这个问题被缩小到了一个页中,比较好解决。而mysql就是用double write来的方法来解决此问题。

由于innodb page是16K,一般系统page是4k,当有个update语句需要对业内记录加1,当第一个4k中记录加1后,系统宕机,重启恢复时候,innodb 不知道从哪里给记录加1,如果给16k里所有记录都加1,就会导致第一个4k里面记录加2,必然导致数据不一致,这个时候double write buffer就解决了这个问题。

  Double Write的思路很简单:

  A. 在覆盖磁盘上的数据前,先将Page的内容写入到磁盘上的其他地方(InnoDB存储引擎中的doublewrite  buffer,这里的buffer不是内存空间,是持久存储上的空间).

  B. 然后再将Page的内容覆盖到磁盘上原来的数据。

  如果在A步骤时系统故障,原来的数据没有被覆盖,还是完整的。
  如果在B步骤时系统故障,原来的数据不完整了,但是新数据已经被完整的写入了doublewrite buffer.  因此系统恢复时就可以用doublewrite buffer中的新Page来覆盖这个不完整的page.