os.File.Sync只同步内核页缓存到块设备写缓存,不保证落盘;断电仍可能丢数据,尤其在write-back缓存开启时。

Go 的 os.File.Sync 到底同步什么?
它只保证内核页缓存(page cache)里的数据刷到块设备的写缓存(如 SSD 控制器缓存),不保证落盘物理介质。换句话说,Sync 后仍可能因断电丢数据——尤其在开启 write-back 缓存的磁盘上。
常见错误现象:Sync 返回 nil,但重启后文件内容“回滚”到上次落盘状态;日志系统误以为已持久化,实际未生效。
- 适用场景:对一致性要求中等、能接受极小概率丢失(如临时状态快照)
- 不适用场景:金融交易日志、WAL 日志、需严格 crash-consistency 的系统
- 底层调用:Linux 下默认映射为
fsync(2),Windows 下为FlushFileBuffers
fdatasync 在 Go 里怎么用?
Go 标准库没直接暴露 fdatasync,得靠 syscall 或第三方封装(如 golang.org/x/sys/unix)。它比 fsync 轻量:跳过文件元数据(mtime/ctime 等)同步,只刷数据块——适合写密集、元数据不变的场景(如追加日志)。
示例(Linux):
立即学习“go语言免费学习笔记(深入)”;
import "golang.org/x/sys/unix" // fd 是 *os.File.Fd() 得到的 int err := unix.Fdatasync(int(fd))
- 性能影响:比
Sync快 10%–30%,尤其在频繁更新文件大小或时间戳时 - 兼容性风险:仅 Linux/macOS 支持;Windows 无对应语义,必须 fallback 到
Sync - 注意:如果文件被截断(
Truncate)或重命名,元数据变更仍需Sync保一致
为什么 Write + Sync 还可能丢数据?
根本原因在于 IO 路径太长:用户空间 → 内核页缓存 → 块设备缓存 → NAND 闪存。每个环节都可能缓冲。
- 硬盘/SSD 自身 write-back 缓存默认开启(
hdparm -I /dev/sdX | grep "Write cache"可查),需hdparm -W0关闭才真正可靠 - 文件系统层(如 ext4)可能启用
data=writeback模式,连元数据都不保证顺序落盘 - Go 的
Write调用返回成功,仅代表数据进了内核页缓存,和磁盘毫无关系
真正要持久化,还得加一层保险
单靠 Sync 或 fdatasync 不够,得配合存储栈配置和写法约束:
- 打开文件时加
os.O_SYNC:让每次Write都隐式触发同步(性能差,但语义强) - 关键路径用
O_DSYNC(通过unix.Open):等价于每次写后fdatasync,比O_SYNC轻量 - 确保文件系统挂载参数不含
barrier=0或commit=过大值 - SSD 要开启
cache flush支持(NVMe 的FLUSHcommand 或 SATA 的FLUSH CACHE EXT),且驱动启用
最易被忽略的是硬件层——很多云盘或 RAID 卡根本不透传 flush 命令,Sync 在那儿空转。真要强持久,得实测断电恢复结果,不能只信返回值。










