Hexo

点滴积累 豁达处之

0%

两次写doublewrite

我们知道,innodb的数据页一般大小是16KB,MySQL存取数据的最小单位也是页,而操作系统并不能保障一个数据页的原子性,也就是说当写入数据时,有可能在一个页中写入一半时(比如8K)数据库宕机,这种情况称为部分写失效(partial page write),从而导致数据丢失。

什么是double write

MySQL的数据页默认是16K,而文件系统的数据页是4K,IO操作是按页为单位就行读写的。这就可能出现数据库对一个16k的数据页修改后,操作系统开始进行写磁盘,但是在这个过程中数据库宕机导致没有完全将16K数据页写到磁盘上。数据库重启后,校验数据页,发现有数据页不完整,就起不来了(redo是基于完整数据页进行的恢复)。
  为了解决这个问题,MySQL引入了double write这个特性。double write针对的是脏数据,提高innodb的可靠性,用来解决部分写失败(partial page write)。为了数据的持久性,脏数据需要刷新到磁盘上,而double write就产生在将脏数据刷盘的过程中。刷盘是一份脏数据写到共享表空间ibdata中,一份写到真正的数据文件永久的保存。写了两次脏数据,就叫double wriete。

double write 原理

简单来说,double write就是将修改后的脏页先放到double write buffer区,这个区占用2M内存空间,buffer空间满或其他条件触发,使double write buffer存的脏页先写到共享表空间(在MySQL 8.0.20之前,doublewrite缓冲区存储区位于InnoDB系统表空间中。从MySQL 8.0.20开始,doublewrite缓冲区存储区位于doublewrite文件中),之后在写入数据文件。这个时候如果写了不完整的页,可以用共享表空间中完整的页加以覆盖,数据页完整了,数据库也就可以拉起了,之后的各种恢复就看redo log的了。redo log是按数据块的方式记录日志的,它是根据偏移量来记录修改。尽管数据被写入两次,但双写缓冲区不需要两倍的I / O开销或两倍的I / O操作。只需一次fsync()调用操作系统。数据就可以按较大的顺序块写入doublewrite缓冲区(除非 innodb_flush_method设置为 O_DIRECT_NO_FSYNC)。
  8.0.20后,在默认情况下,为每个缓冲池实例创建两个双写文件:刷新列表双写文件和LRU列表双写文件。

刷新列表双写文件用于从缓冲池刷新列表中刷新的页面。刷新列表doublewrite文件的默认大小是InnoDB page size * doublewrite page bytes.。

LRU列表双写文件用于从缓冲池LRU列表中刷新的页面。它还包含用于单页刷新的插槽。LRU列表双写文件的默认大小为InnoDB page size * (doublewrite pages + (512 / the number of buffer pool instances)),其中512是为单页刷新保留的插槽总数。

  至少有两个双写文件。双写文件的最大数量是缓冲池实例数量的两倍。
redolog写入的单位就是512字节,也就是磁盘IO的最小单位,所以无所谓数据损坏。

Mysql_doublewrite01

double write恢复流程

数据恢复有三种情况

脏数据写磁盘成功

  刷盘成功,找检查点,进行前滚、回滚就行了。

共享表空间ibdata写失败

  如果是写共享表空间失败,那么这些数据不会被写到数据文件,数据库会认为这次刷盘从没发生过,MySQL此时会从磁盘载入原始的数据,然后找检查点,redo log前滚、回滚就行了。

脏数据刷数据文件失败

  写共享表空间成功,但是写数据文件失败,在恢复的时候,MySQL直接比较页面的checksum,如果不对的话,直接从共享表空间的double write中找到该页的一个最近的副本,将其复制到表空间文件,再应用redo log,就完成了恢复过程。因为有副本所以也不担心表空间中数据页是否损坏。

double write 负载

double write是一个buffer, 但其实它是开在物理文件上的一个buffer, 其实也就是file, 所以它会导致系统有更多的fsync操作, 而硬盘的fsync性能是很慢的, 所以它会降低mysql的整体性能。

2、监控double write工作负载

1
2
3
4
5
6
7
8
mysql> show global status like '%dblwr%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Innodb_dblwr_pages_written | 44 |
| Innodb_dblwr_writes | 8 |
+----------------------------+-------+
2 rows in set (0.00 sec)

关注点:Innodb_dblwr_pages_written / Innodb_dblwr_writes
  开启doublewrite后,每次脏页刷新必须要先写doublewrite,而doublewrite存在于磁盘上的是两个连续的区,每个区由连续的页组成,一般情况下一个区最多有64个页,所以一次IO写入应该可以最多写64个页。

适合关闭double write场景

  • 海量DML
  • 不惧怕数据损坏和丢失
  • 系统写负载成为主要负载

相关参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> show variables like '%double%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| innodb_doublewrite | ON |
| innodb_doublewrite_batch_size | 0 |
| innodb_doublewrite_dir | |
| innodb_doublewrite_files | 2 |
| innodb_doublewrite_pages | 8 |
+-------------------------------+-------+
5 rows in set (0.00 sec)

#innodb_doublewrite:是否开启doublewrite
#innodb_doublewrite_batch_size:定义要批量写入的双写页面数
#innodb_doublewrite_dir:定义双写缓冲区文件目录
#innodb_doublewrite_files:定义双写文件的数量
#innodb_doublewrite_pages:定义批量写入时每个线程的最大双写页数

参考链接