doublewrite buffer通过顺序写双写区+完整页副本+崩溃后校验修复,绕开partial page write物理死结;partial page write指16kb页被拆为多次4kb/512b写入,断电导致页半新半旧、校验失败,而o_direct/fsync/raid/nvme/ups均无法保证整页原子落盘。

Doublewrite Buffer 能防止页损坏,不是因为它“更可靠”,而是它用顺序写 + 完整副本 + 崩溃后主动校验的组合,绕开了部分页写入(partial page write)这个物理层死结。关掉它,InnoDB 就失去对 16KB 页完整性的兜底能力。
partial page write 是什么,为什么操作系统拦不住
InnoDB 默认页大小是 16KB,而 Linux 文件系统(如 ext4、XFS)底层以 4KB 为单位刷盘,硬盘/SSD 物理扇区更是 512B 或 4KB。一次 write() 系统调用看似原子,实际被拆成 4 次独立 I/O。断电可能卡在第 2 次之后——结果就是磁盘上那个页前 8KB 是新数据,后 8KB 是旧垃圾或全零。
此时该页的 page checksum 必然失效,InnoDB 启动时直接报错:InnoDB: Database page corruption on disk 或拒绝加载。
-
O_DIRECT或fsync()都不能解决:它们只保证“数据已落盘”,不保证“整页原子落盘” - RAID10、NVMe SSD、UPS 也无效:RAID 保块级原子,SSD 掉电保护有盲区,UPS 不防进程崩溃
- 文件系统 barrier(如
XFS的xfs_io -c "sync")只管元数据顺序,不管单页物理完整性
Doublewrite Buffer 怎么实际拦截 partial page write
它不检测、不修复、不重试,而是靠“先存副本、再写正本”的物理顺序把风险前置。
- 刷脏页时,
memcpy把整页(16KB)拷进内存doublewrite buffer(默认128页,共2MB) - 再把这
2MB内存分两次、每次1MB,顺序写入磁盘双写区(ib_dblwr_1或ibdata1中连续空间),并调用fsync()落盘 - 只有双写区写成功,才开始把该页随机写入目标
.ibd文件
关键点在于:双写区是连续空间、顺序 I/O,极难中断;而目标写是离散随机 I/O,易受断电打断。只要双写区那 1MB 写成功了,哪怕后面写 .ibd 时断电,重启也能从双写区原样恢复出完整页——页断裂根本没机会落地。
为什么 redo log 和 binlog 都救不了断裂页
redo log 记录的是“对某页某 offset 执行了什么修改”,前提是该页结构完好、能解析出 page_no、space_id、LSN。断裂页连页头都可能被截断或校验和错,InnoDB 连这个页属于哪张表都识别不出来,redo log 直接跳过或报错。
-
binlog更不相关:它只记录逻辑语句(如UPDATE t SET x=1 WHERE id=5),不保存任何物理页副本 -
undo log也无效:它只存事务前镜像,不解决页结构损坏问题 - 没有
doublewrite buffer,崩溃后唯一出路就是从备份恢复,且最近几秒事务必然丢失
确认 doublewrite 是否真在工作,别只看配置
innodb_doublewrite = ON 在配置文件里写了,不代表运行时生效;MySQL 启动时可能被忽略或覆盖。
- 查变量:
SHOW VARIABLES LIKE 'innodb_doublewrite';—— 必须返回ON - 查状态:
SHOW STATUS LIKE 'Innodb_dblwr%';,重点关注:Innodb_dblwr_writes(双写区写入次数)和Innodb_dblwr_pages_written(写入页数) - 若两者长期为
0,说明机制未触发:可能是实例刚启动、还没到 checkpoint,也可能是配置被覆盖或 MySQL 版本不支持(如老版本未启用) - MySQL 8.0.20+ 若启用了
innodb_redo_log_capacity自动管理,Innodb_dblwr_writes可能延迟出现,等第一个 checkpoint 完成后才开始计数
真正容易被忽略的是:双写缓冲区的磁盘副本必须 fsync 成功才算数,而很多监控只盯 Innodb_dblwr_writes 计数,却不管 fsync() 是否真落盘——如果存储层吞掉了 fsync(如某些虚拟化环境或 misconfigured SSD),双写就形同虚设。











