根本原因是 SSD 以 4KB page 为最小读写单位,小写需读 - 改 - 写整页,导致 IO 放大;机械盘则受限于 sector(512B 或 4KB)对齐要求。

为什么 写 1 个 字节,磁盘实际要写 4KB?
根本原因在于硬件层的最小操作单元限制:SSD 以page(通常 4KB)为读写基本单位,机械盘则以sector(传统 512B,现多为 4K native)为准。当你发起一个未对齐的write()——比如偏移量是 4097 字节、长度 1 字节——底层无法只刷 1 字节,必须:
- 读出整个目标
page(4KB)到 SSD 控制器缓存; - 在内存中合并新数据;
- 擦除原
page(SSD 不可覆写); - 把整页 4KB 写入新位置。
这 1 次逻辑写,触发了 1 次读 + 1 次擦 + 1 次写 → 实际 IO 放大 3 倍以上。更糟的是,若该 page 跨两个物理erase block,还可能引发额外迁移,进一步放大。
文件系统和分区不对齐,会雪上加霜
即使应用层写请求对齐,如果底层布局没对齐,照样放大。典型场景:
- 分区起始扇区不是 4096 字节(8×512B)的整数倍 →
fdisk -l看Start列,若不是 8 的倍数,就是错的; - ext4 格式化时没指定
-E stride=128,stripe-width=256等 RAID 感知参数,导致元数据分布打乱对齐; - LVM 物理扩展
PE大小(默认 4MB)与 SSDpage或erase block不匹配,中间多一层映射损耗。
结果是:你明明用 O_DIRECT 写了 4KB 对齐 buffer,内核下发到底层的 bio 却仍被拆成多个非对齐 request。
iostat -x里哪些指标暴露了读写放大?
光看 wkB/s 和w/s不够,关键要看放大比:
- 计算平均写大小:
avgqu-sz / (w/s)或直接看avgrq-sz(单位扇区);若长期(即- 对比
r/s和w/s:SSD 负载下r/s ≪ w/s但rkB/s ≈ wkB/s,大概率在后台 GC 或 rewrite;- 观察
%util高但await异常飙升(如>50ms):说明请求在队列堆积,背后常是频繁的 read-modify-write 循环。 - 对比
注意:iostat不显示底层擦除次数,需结合 smartctl -a /dev/nvme0n1 | grep -i "media wear" 看 SSD 磨损指标佐证。
避免放大的实操底线
不是所有场景都能根治,但守住这三条能拦住 80% 问题:
- 新建分区时强制 4K 对齐:
fdisk /dev/sda→g(GPT)→n→ 回车让起始扇区默认从 2048 开始(=1MB 对齐); - SSD 上禁用
barrier和journal开销(仅限数据盘):mkfs.ext4 -O ^has_journal /dev/sda1,挂载加noatime,nodiratime,discard; - 应用写文件前检查 buffer 地址和 offset:
posix_memalign(&buf, 4096, size)+lseek(fd, offset & ~4095, SEEK_SET),再write()。
真正难缠的从来不是单次写放大,而是日志型应用(如 WAL、binlog)持续小写 +fsync,这种必须配合 io_uring 提交聚合或换用 libpmem 直写持久内存——普通块设备上,对齐只是起点,不是终点。






























