MySQL 为什么需要双写缓冲区,redo log 为何不双写
InnoDB 里,双写缓冲区一直是个很容易被面试官追问细节的话题。很多人知道它'用来防止页损坏',但一旦继续往下问:为什么需要它、为什么 redo log 不能替代它、4MB 这个说法从哪来,答案就容易散掉。
这篇内容把这几个点串起来讲清楚。
先看 InnoDB 的存储方式
InnoDB 的数据并不是按'行'直接落盘的,而是按 页(page) 来管理。默认一个页是 16KB,磁盘和内存之间的读写,也都是围绕页来进行的。
InnoDB 的核心结构可以简单理解为两部分:
- 内存结构:Buffer Pool、Redo Log Buffer、Change Buffer 等
- 磁盘结构:表空间、redo log 文件、doublewrite 相关文件等
其中,Buffer Pool 负责把热点数据留在内存里,减少磁盘访问;Redo Log 负责保证事务提交后的崩溃恢复;Doublewrite Buffer 则负责避免'页写了一半就断电'这种更底层的问题。
为什么写页时会有'双写'
问题出在页刷盘这一步。
MySQL 运行在 Linux 上,InnoDB 的一个数据页是 16KB,但操作系统的页通常是 4KB。也就是说,InnoDB 把一个 16KB 的页真正写到磁盘时,底层其实要拆成多个 4KB 的写入动作。
这就带来一个经典风险:部分页写入(Partial Page Write)。
比如一个页准备刷盘时,前几个 4KB 已经写进去了,写到中间突然断电,结果就是这个 16KB 页只有一部分落盘成功,另一部分还是旧数据。这样写出来的页既不是'旧的完整页',也不是'新的完整页',而是一个损坏的中间态。
这类问题,redo log 解决不了。
原因很直接:redo log 记录的是'对页做了什么物理修改',不是整页快照。它更擅长的是在页本身完整的前提下,重放修改;可一旦页已经被写坏成半残状态,redo log 只能告诉你应该改哪里,却没法把一个缺胳膊少腿的页直接拼回完整状态。
所以,InnoDB 需要一个额外的安全垫:Doublewrite Buffer。
Doublewrite Buffer 是怎么工作的
Doublewrite Buffer 的思路很朴素,也很有效:
先把要刷盘的数据页,统一写到一个连续区域里;确认这一步完成后,再写到它真正该去的地方。
它包含两部分:
- 内存中的 doublewrite buffer:通常是 128 个页,也就是 2MB
- 磁盘上的 doublewrite 区域:位于系统表空间中,也是 128 个页,大小同样是 2MB
当某批脏页要刷盘时,流程大致是这样的:
- 先把这些页拷贝到内存中的 doublewrite buffer。
- 再把 doublewrite buffer 里的内容顺序写到磁盘上的 doublewrite 区域。
- 最后再把这些页写回真正的数据文件
.ibd。
这里真正有价值的是第 2 步。写 doublewrite 区域时,数据是连续、顺序写入的,性能相对稳定,而且一旦这一步完成,InnoDB 就等于先拿到了一份'完整副本'。
如果后面把页写回数据文件时发生断电,InnoDB 恢复时可以直接从 doublewrite 区域里找回这份完整页,再继续结合 redo log 做后续恢复。
这就是'双写'名字的来源:同一份页数据会先后写两次到磁盘。
为什么 redo log 不能替代 doublewrite
这是面试里最容易被混在一起的地方。
redo log 解决的是'事务恢复'
redo log 的目标是:
- 让事务提交后,即使数据库崩溃,也能把已经提交的修改重放回来
- 把随机写尽量变成顺序写,提升性能
它记录的不是整页,而是页上的修改信息,例如:


