根本原因是防止“部分页写入”导致页损坏,而redo log仅记录逻辑变更、无法修复结构已乱的断裂页。

MySQL InnoDB 需要 Doublewrite Buffer,根本原因只有一个:防止“部分页写入”(Partial Page Write)导致的页损坏,而这种损坏无法靠 redo log 修复。
为什么 redo log 不能修复损坏的页?
redo log 记的是逻辑变更,比如“把 balance 字段加 100”,不是整页的二进制快照。一旦页在写入磁盘时只写了一半(例如 16KB 页只落盘了前 8KB),这个页的结构就乱了——校验和失效、B+ 树节点可能断裂、甚至无法解析。此时 redo log 没有合法的原始页可依附,应用失败。
常见错误现象包括:
- MySQL 启动时报错:
InnoDB: Database page corruption on disk - 查询某张表时触发
Assertion failure或直接崩溃 -
mysqld进程异常退出后无法重启,日志反复提示页校验失败
为什么 O_DIRECT 或 fsync 也不能替代 Doublewrite?
即使配置了 innodb_flush_method=O_DIRECT,或每次刷页都调用 fsync(),仍无法保证单次 16KB 写入的原子性。因为:
- 底层存储设备(SATA SSD、HDD、甚至部分 NVMe)的物理原子写单元仍是 512B 或 4KB,不是 16KB
-
O_DIRECT_NO_FSYNC更危险:跳过fsync,连双写文件本体都可能丢失 - 文件系统(如 ext4/XFS)不承诺
write()系统调用对应整页落盘;它只保证数据进了磁盘缓存,不保证没断电前全写完
换句话说:你告诉内核“写 16KB”,内核拆成 4 次 4KB 的 I/O;断电发生在第 3 次之后,结果就是“3/4 新 + 1/4 旧”——这就是典型的断裂页(torn page)。
MySQL 8.0.20+ 的双写文件 vs 旧版 ibdata1 中的双写区
从 MySQL 8.0.20 起,Doublewrite Buffer 的磁盘载体不再是系统表空间 ibdata1,而是独立文件(默认名如 ib_dblwr_1、ib_dblwr_2)。这不是为了性能提升,而是运维解耦:
-
ibdata1不再因双写区预留而持续膨胀,避免“无法 shrink”的历史痛点 - 可通过
innodb_doublewrite_files控制文件数量,提升高并发刷脏页时的并行写能力 - 实例停机后可安全删除或迁移双写文件,无需导出整个系统表空间
注意:innodb_doublewrite_buffer_size 控制的是内存中缓冲区大小(默认 2MB,即 128 × 16KB),和磁盘上双写文件的总容量无关。
innodb_doublewrite=OFF / DETECT_ONLY 的真实风险
OFF 是彻底关闭:既不写页内容,也不写元数据。崩溃后完全依赖 redo log + 正常页恢复,遇到部分写失败就会报错、丢数据,甚至拒绝启动。
DETECT_ONLY(MySQL 8.0.30+)只写元数据(如页号、LSN),不写实际页内容。它不参与恢复,仅用于事后诊断是否发生过部分写——等于关掉了防护,只留了个监控探针。
真正容易被忽略的一点是:很多压测场景下人为关闭 Doublewrite,却忘了在生产上线前打开。而部分写问题往往在高负载 + 断电/宕机时才暴露,那时已晚。











