敲黑板:InnoDB的Double Write,你必须知道
前序
InnoDB引擎有几个重点特性,为其带来了更好的性能和可靠性:
-
插入缓冲(Insert Buffer)
-
两次写(Double Write)
-
自适应哈希索引(Adaptive Hash Index)
-
异步IO(Async IO)
-
刷新邻接页(Flush Neighbor Page)
今天我们的主题就是 两次写(Double Write)
, 先一句话概括下:
上一次我们讲过Insert Buffer 是用来提高存储引擎性能上的提升,Double Write 就是为了在数据库崩溃恢复时保证数据不丢失的一个重要特性,保证了数据的可靠性。
概念点
如图,还是先来说几个基础的概念:
-
数据库表空间由段(segment)、区(extent)、页(page)组成
-
默认情况下有一个共享表空间ibdata1,如使用了innodb_file_per_table则每张表独立表空间(指存放数据、索引、插入缓冲bitmap页)
-
段包括了数据段(B+树的叶子结点)、索引段、回滚段
-
区,由连续的页组成,任何情况下每个区都为1M,一个区中有64个连续页(16k)
-
页,数据页(B-tree Node)默认大小为16KB
-
文件系统一页 默认大小为4KB
-
盘片被分为许多扇形的区域,每个区域叫一个扇区,硬盘中每个扇区的大小固定为512字节
-
脏页,当数据从磁盘加载到缓冲池的数据页后,数据页内容被修改后,此数据页称为脏页
出现的问题
通过上次讲的 重要,知识点:InnoDB的插入缓冲 我们知道,脏页会在某些场景下进行刷盘,将缓冲池内的脏页数据落地到磁盘。
因为存储引擎缓冲池内的数据页大小默认为16KB,而文件系统一页大小为4KB,所以在进行刷盘操作时,就有可能发生如下场景:
如图所示,数据库准备刷新脏页时,需要四次IO才能将16KB的数据页刷入磁盘。
但当执行完第二次IO时,数据库发生意外宕机,导致此时才刷了2个文件系统里的页,这种情况被称为写失效(partial page write)。
此时重启后,磁盘上就是不完整的数据页,就算使用redo log也是无法进行恢复的。
注意:
-
redo log无法恢复数据页损坏的问题,恢复必须是数据页正常并且redo log正常。
-
这里要知道一点,redo log中记录的是对页的物理操作,如偏移量600,写'xxxx'记录。
-
如果这个页本身已经发生了损坏,再对其进行重做是没有意义的
该怎么解决这个问题
那应该怎么来解决这个问题呢?其实大家想一下就会有个大概的答案,就是给它搞个备份呗。
如果写脏页的时候发生宕机,在重启后使用下备份先恢复下数据页在写磁盘就可以了,其实这就是Double Write
。
Double Write 出现
千呼万唤始出来,为了防止我们可怜的数据被破坏,InnoDB存储引擎提供了重要的Double Write 特性,避免了数据丢失的惨剧发生。
下面我们来慢慢的来看看Double Write 到底是怎么提高可靠性的
Double Write 解决的问题
在数据库进行脏页刷新时,如果此时宕机,有可能会导致磁盘数据页损坏,丢失我们重要的数据。此时就算重做日志也是无法进行恢复的,因为重做日志记录的是对页的物理修改。
其实就是在重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是double write。
Double Write 架构
如图,其实Double Write 分为了两个组成部分:
-
内存中的double write buffer -
物理磁盘上共享表空间中连续的128个页,即2个区(extent),大小同样为2MB
可以看出,有了Double write后的脏页刷新流程就是多了几步操作:
-
在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先复制到内存中的Double write buffer
-
通过Double write buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题
Double write崩溃恢复
如图,如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB存储引擎可以从共享表空间中的Double write中找到该页的一个副本,将其复制到表空间文件,再应用重做日志。
下面显示了一个由Double write进行恢复的情况:
090923 12:36:32 mysqld restarted
090923 12:26:33 InnoDB: Database was not shut down normally!
InnoDB: Starting crash recovery.
InnoDB: Reading tablespace information from the .ibd files...
InnoDB: Crash recovery may have faild for some .ibd files!
InnoDB: Restoring possible half-written data pages from the doublewrite.
InnoDB: buffer...
Double Write 的问题
Double write buffer 它是在物理文件上的一个buffer, 其实也就是file,所以它会导致系统有更多的fsync操作,而因为硬盘的fsync性能问题,所以也会影响到数据库的整体性能。
Double write页是连续的,因此这个过程是顺序写的,开销并不是很大。
在完成Double write页的写入后,再将Double write buffer中的页写入各个数据文件中,此时的写入则是离散的
总结
-
当commit 一个修改语句时,如果redo log有空闲区域,直接写redo log,如果redo log没有空闲区域,那么需要把被覆盖的redo log对应的数据页刷新到data file 中,最后改pool buffer中的记录
-
innodb的redo log 不会记录完整的一页数据,因为这样日志太大,它只会记录那次(sequence)如何操作了(update,insert)哪页(page)的哪行(row)
-
因为数据库使用的页(page,默认16KB)大小和操作系统对磁盘的操作页(page,默认4KB)不一样,当提交了一个页需要刷新到磁盘,会有多次IO, 此时刷了前面的8k时异常发生宕机。在系统恢复正常后,如果没有double write机制,此时数据库磁盘内的数据页已损坏,无法使用redo log进行恢复。
-
如果有double write buffer,会检查double writer的数据的完整性,如果不完整直接丢弃double write buffer内容,重新执行那条redo log,如果double write buffer的数据是完整的,用double writer buffer的数据更新该数据页,跳过该redo log。
往期推荐
本文分享自微信公众号 - 架构技术专栏(jiagoujishu)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
强化学习的最基本概念马尔可夫决策过程简介
在本文中我将介绍强化学习的基本方面,即马尔可夫决策过程。我们将从马尔可夫过程开始,马尔可夫奖励过程,最后是马尔可夫决策过程。 目录 马尔可夫过程 马尔可夫奖励过程 马尔可夫决策过程 马尔可夫过程 马尔可夫决策过程(MDP)代表了一种强化学习的环境。我们假设环境是完全可见的。这意味着我们拥有了当前状态下做出决定所需的所有信息。然而,在我们讨论MDP是什么之前,我们需要知道马尔科夫性质的含义。 马尔可夫性质指出,未来是独立于过去的现在。它意味着当前状态从历史记录中捕获所有相关信息。例如,如果我现在口渴了,我想马上喝一杯。当我决定喝水的时候,这与我昨天或一周前口渴无关(过去的状态)。现在是我做出决定的唯一关键时刻。 鉴于现在,未来独立于过去 除了马尔可夫性质外,我们还建立了一个状态转移矩阵,它存储了从每个当前状态到每个继承状态的所有概率。假设我在工作时有两种状态:工作(实际工作)和观看视频。当我工作时,我有70%的机会继续工作,30%的机会看视频。然而,如果我在工作中看视频,我可能有90%的机会继续看视频,10%的机会回到实际工作中。也就是说,状态转移矩阵定义了从所有状态(工作,观看视频)到...
- 下一篇
实现nodejs进程间通信
对于有继承关系的进程,nodejs本身为我们提供了进程间通信的方式,但是对于没有继承关系的进程,比如兄弟进程,想要通信最简单的方式就是通过主进程中转,类似前端框架中子组件通过更新父组件的数据,然后父通知其他子组件。因为nodejs内置的进程间通信需要经过序列化和反序列化,所以这种方式可能会带来一定的性能损耗,而且在实现上也比较麻烦。今天介绍的是实现兄弟进程通信的另外一种方式,在windows上使用命名管道,在非windows上使用unix域,另外本文还会介绍基于tcp的远程进程通信的实现。下面具体介绍一下设计和实现。 1 IPC的实现 ipc的实现比较简单,主要是对nodejs提供的功能进行封装。首先我们需要处理一下path,因为在命名管道和unix域中他的格式是不一样的。 const os = require('os');module.exports = { path: os.platform() === 'win32' ? '\\\\?\\pipe\\ipc' : '/tmp/unix.sock',}; 接着我们看看客户端和服务器的实现。 1.1 IPCClient的实现 cons...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2整合Redis,开启缓存,提高访问速度