跳到主要内容

MySQL 学习(6)Innodb Double Write

Double Write(两次写)是什么?​

如果说 ibuf(Insert Buffer)带给 InnoDB 存储引擎的是性能上的提升,那么 doublewrite(两次写)带来的则是数据页的可靠性。

InnoDB 的 Page Size 一般是 16KB,其数据校验也是针对这 16KB 来计算的,将数据写入到磁盘是以 Page 为单位进行操作的。

我们知道磁盘在写入时,都是以 512 字节为单位,不能保证 MySQL 数据页面 16KB 的一次性原子写。试想,在某个 Dirty Page flush 的过程中,发生了系统断电(或者OS崩溃),16K 的数据只有 8K 被写到磁盘上,只有一部分写是成功的,这种现象被称:部分写失效(partial page write)。

一旦 partial page writes 发生,那么在 InnoDB 恢复时就很尴尬:在 InnoDB 的 Redo Log file(重做日志文件)中虽然知道这个数据页被修改了,但是却无法知道这个页被修改到什么程度,和这个页面相关的 redo 也就无法应用了,也就是说如果出现了偏移量问题,再对进行重做就没有意义了。

重做日志中记录的是对页的物理操作,如偏移量 800,写 aaaa 记录。如果这个页本身已经发生了损坏,再对其进行重做是没有意义的。这就是说,在应用重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是 doublewrite。

不需要担心这个 Double Write 导致空间满了,两次写在任何时候记录的都是数据库最后发生改变的若干页面(最多128个),在数据库不断工作的过程中,它会不断的被覆盖,它始终是最新的数据,记录的是修改之后的页面数据,而不是修改之前的数据,它的作用不是还原数据,而是保证不会丢失修改。

double write 的组成部分​

1、一部分是 InnoDB 内存中的 double write buffer,大小为 2M;

2、另一部分是物理磁盘上 ibdata 系统表空间中大小为 2MB,共 128 个连续的 Page,既 2 个分区。其中 120 个用于批量写脏,另外 8 个用于 Single Page Flush。做区分的原因是批量刷脏是后台线程做的,不影响前台线程。而 Single page flush 是用户线程发起的,需要尽快的刷脏并替换出一个空闲页出来。

double write 的执行步骤​

1、当一系列机制(main 函数触发、checkpoint 等)触发数据缓冲池中的脏页进行刷新到 data file 的时候,并不直接写磁盘,而是会通过 memcpy 函数将脏页先复制到内存中的 double write buffer

2、之后通过 double write buffer 再分两次、每次 1MB 顺序写入共享表空间的物理磁盘上。

3、然后马上调用 fsync 函数,同步脏页进磁盘上。由于在这个过程中,double write 页的存储是连续的,因此写入磁盘为顺序写,性能很高;

4、完成 double write 后,再将脏页写入实际的各个表空间文件,这时写入就是离散的了。

各模块协作情况如下图(第一步应为脏页产生的 redo 记录 log buffer,然后 log buffer 写入 redo log file,为简化次要步骤直接连线表示):

补充知识:ibdata 文件​

InnoDB 中有共享表空间和独立表空间的概念。共享表空间就是 ibdata1,独立表空间放在每个表的 .ibd(数据和索引)和 .frm(表结构)为后缀的文件中。

共享表空间:

独立表:

单独的表空间只存储该表的数据,索引和插入缓冲的 BITMAP 等信息,其余还放在共享表空间中

ibdata1 里面存储了什么内容?

  • Data dictionary
  • Double write buffer
  • Insert buffer
  • Rollback segments
  • UNDO space
  • Forign key constraint system tables

ibdata1 暴涨的原因?

  • 大量事务,产生大量的 undo log
  • 有旧事物长时间未提交,产生大量旧 undo log
  • file I/O 性能差,purge 进展慢
  • 32bit系统下bug

double write 还原数据​

在应用重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是 doublewrite。

double write 总结:​

1、两次写在任何时候记录的都是数据库最后发生改变的若干页面(最多128个),在数据库不断工作的过程中,它会不断的被覆盖,它始终是最新的数据,记录的是修改之后的页面数据,而不是修改之前的数据,它的作用不是还原数据,而是保证不会丢失修改。

2、至于性能问题,表面看上去,它是每一个页面都写了 2 遍,则会非常影响性能,但实际上,由于将所写的页面都先缓存到内存中,到达 128 个之后才真正写入,那么对于磁盘而言,连续写与分散写(每个页面自己写)的性能相差很大的,而两次写正是将一个簇数量的页面组合起来形成 2 个连续的空间 写入到两次写空间中,有效的利用这了这特点,所以性能是不会相差 1 倍的。实际上经过测试,可能两次写使得性能降低了 10%。

3、有其它一些数据库完全没有类似 2 次写的问题,比如达梦等,这个主要是由于它们采用了全物理的 REDO,将一个页面的写操作都拆成一个个的小的物理写入,这种情况下就不会存在写断裂的情况,因为不管怎么写,日志都是对一个页面操作的重演,在 REDO 做完之后,页面的状态肯定是正确的。