HDD 一次随机访问要 10ms,SSD 只要 0.1ms——100 倍的差距。但 SSD 也有自己的代价:写入前必须先擦除整个块,实际写入磁盘的数据量远大于用户提交的数据量。一块标称 1TB 的 SSD,在极端写入模式下可能提前耗尽寿命。
这些数字不是凭空出现的。它们来自物理层——HDD 的磁头寻道、盘片旋转,SSD 的 NAND 闪存擦写、FTL 映射。存储系统的每一层设计决策,都受制于这些物理约束。
一、HDD 磁盘结构
1.1 磁盘的物理组成
一块典型的 HDD 由以下部分组成:
| 组件 | 说明 | 对性能的影响 |
|---|---|---|
| 盘片(Platter) | 双面涂覆磁性材料的铝合金/玻璃圆盘 | 盘片数决定容量 |
| 磁头(Head) | 读写数据的电磁转换器 | 每面一个磁头 |
| 磁臂(Actuator Arm) | 带动磁头移动的机械臂 | 所有磁头同步移动 |
| 主轴(Spindle) | 带动盘片旋转 | RPM 决定旋转延迟 |
| 磁道(Track) | 盘面上的同心圆 | 密度决定容量 |
| 扇区(Sector) | 磁道上的最小寻址单元 | 512B 或 4KB |
| 柱面(Cylinder) | 所有盘面同一位置的磁道集合 | 柱面内切换无需寻道 |
1.2 HDD 访问延迟分解
HDD 的访问延迟由三个部分组成:
| 延迟组成 | 含义 | 典型值 | 计算方式 |
|---|---|---|---|
| 寻道延迟 | 磁臂移动到目标磁道 | 3–15 ms | 取决于磁头移动距离 |
| 旋转延迟 | 等待目标扇区旋转到磁头下方 | 2–6 ms | |
| 传输延迟 | 数据从盘面读取/写入 | 0.01–0.1 ms | 数据量 / 传输速率 |
# HDD 延迟计算示例def hdd_access_time(rpm=7200, seek_distance="average", data_size=4096): """计算 HDD 访问延迟""" # 寻道延迟 seek_times = { "track_to_track": 0.5, # ms, 相邻磁道 "average": 4.16, # ms, 平均寻道 "full_stroke": 10.0, # ms, 全程寻道 } t_seek = seek_times[seek_distance]
# 旋转延迟(平均半圈) t_rotation = 0.5 * (60 / rpm) * 1000 # ms
# 传输延迟 transfer_rate = 200 # MB/s (外圈) t_transfer = (data_size / (1024 * 1024)) / transfer_rate * 1000 # ms
total = t_seek + t_rotation + t_transfer
print(f"寻道延迟: {t_seek:.2f} ms") print(f"旋转延迟: {t_rotation:.2f} ms") print(f"传输延迟: {t_transfer:.4f} ms") print(f"总延迟: {total:.2f} ms") print(f"寻道占比: {t_seek/total*100:.1f}%") print(f"旋转占比: {t_rotation/total*100:.1f}%")
hdd_access_time()# 寻道延迟: 4.16 ms# 旋转延迟: 4.17 ms# 传输延迟: 0.0195 ms# 总延迟: 8.35 ms# 寻道占比: 49.8%# 旋转占比: 49.9%HDD 访问延迟的 99.9% 来自机械运动(寻道 + 旋转),数据传输几乎可以忽略。这就是为什么 HDD 的顺序读写比随机读写快几百倍——顺序读写只需一次寻道,而随机读写每次都需要寻道+等待旋转。
1.3 HDD 的性能特征
| 操作 | 延迟 | 吞吐量 | IOPS |
|---|---|---|---|
| 顺序读 | ~5 ms(首次) | 100–250 MB/s | — |
| 顺序写 | ~5 ms(首次) | 100–200 MB/s | — |
| 随机读 4KB | ~10 ms | ~0.4 MB/s | ~100 |
| 随机写 4KB | ~10 ms | ~0.4 MB/s | ~100 |
# 使用 fio 测试 HDD 性能# 顺序读fio --name=seq-read --rw=read --bs=1M --size=1G --numjobs=1 --runtime=60
# 随机读fio --name=rand-read --rw=randread --bs=4K --size=1G --numjobs=1 --runtime=60 \ --iodepth=1 --ioengine=libaio
# 结果示例(7200 RPM HDD):# 顺序读: ~180 MB/s# 随机读: ~0.4 MB/s (~100 IOPS)# 差距: 450x二、NAND 闪存结构
2.1 SSD 的层次结构
NAND 闪存有三个关键层次:
| 层次 | 大小 | 操作 | 说明 |
|---|---|---|---|
| 页(Page) | 4KB / 8KB / 16KB | 读、写 | 最小读写单位 |
| 块(Block) | 256–1024 页 | 擦除 | 最小擦除单位 |
| 平面(Plane) | 数百–数千块 | — | 可并行操作 |
NAND 闪存的核心约束:写前必须擦除,擦除以块为单位。这意味着如果你只想修改 1 字节,也必须:读取整个块 → 修改目标字节 → 擦除整个块 → 写回整个块。这就是”写入放大”的物理根源。
2.2 NAND 闪存的类型
| 类型 | 全称 | 每单元比特数 | P/E 次数 | 速度 | 成本 | 用途 |
|---|---|---|---|---|---|---|
| SLC | Single-Level Cell | 1 bit | ~100,000 | 最快 | 最高 | 企业级/军工 |
| MLC | Multi-Level Cell | 2 bits | ~3,000 | 快 | 中 | 消费级 SSD |
| TLC | Triple-Level Cell | 3 bits | ~1,000 | 一般 | 低 | 主流消费级 |
| QLC | Quad-Level Cell | 4 bits | ~100–300 | 慢 | 最低 | 归档/大容量 |
# SSD 寿命计算def ssd_endurance(capacity_tb=1, nand_type="TLC", dwpd=1): """计算 SSD 寿命""" pe_cycles = {"SLC": 100000, "MLC": 3000, "TLC": 1000, "QLC": 200}
# 总写入量 = 容量 × P/E 次数 total_write_tb = capacity_tb * pe_cycles[nand_type]
# 每日写入量(DWPD = Drive Writes Per Day) daily_write_tb = capacity_tb * dwpd
# 寿命(天) lifetime_days = total_write_tb / daily_write_tb lifetime_years = lifetime_days / 365
print(f"NAND 类型: {nand_type}") print(f"容量: {capacity_tb} TB") print(f"P/E 次数: {pe_cycles[nand_type]:,}") print(f"总写入量: {total_write_tb:,.0f} TB") print(f"每日写入: {daily_write_tb} TB (DWPD={dwpd})") print(f"预计寿命: {lifetime_years:.1f} 年")
ssd_endurance(1, "TLC", 1)# NAND 类型: TLC# 容量: 1 TB# P/E 次数: 1,000# 总写入量: 1,000 TB# 每日写入: 1 TB (DWPD=1)# 预计寿命: 2.7 年2.3 写入放大详解
写入放大(Write Amplification, WA)是 SSD 最核心的性能问题:
写入放大的来源:
| 来源 | 说明 | WA 贡献 |
|---|---|---|
| 页对齐 | 写入必须对齐到页大小 | 1–2x |
| GC 搬迁 | 垃圾回收时搬迁移数据 | 1–3x |
| FTL 映射 | 映射表更新 | 1–1.5x |
| 磨损均衡 | 冷热数据搬移 | 1–1.5x |
| Compaction | 数据库层的合并操作 | 1–10x |
| 总 WA | — | 2–20x |
写入放大直接消耗 SSD 的 P/E 次数。一块 TLC SSD 标称 DWPD=1 可用 2.7 年,但如果工作负载以随机小写入为主(如 WAL、LSM Compaction),实际 WA 可能达到 10x 以上,寿命缩短到不足 1 年。生产环境中务必通过 nvme smart-log 监控 Percentage Used 字段——当该值接近 100% 时应立即更换盘,否则会触发只读保护导致业务中断。
三、FTL 闪存转换层
3.1 FTL 的核心功能
FTL(Flash Translation Layer)是 SSD 的”操作系统”,负责在逻辑块地址(LBA)和物理页地址(PPA)之间做映射:
// FTL 核心数据结构(简化)struct FTL { // 逻辑到物理的映射表 // LBA (Logical Block Address) → PPA (Physical Page Address) ppa_t *mapping_table; // 通常存在 DRAM 中
// 块信息表 struct BlockInfo { int valid_page_count; // 有效页数 int erase_count; // 擦除次数(磨损均衡) block_state_t state; // FREE/IN_USE/GC } *block_info;
// 垃圾回收器 struct GC { int threshold; // 触发 GC 的空闲块阈值 gc_policy_t policy; // GC 策略(贪心/成本效益) };};
// 写入流程void ftl_write(FTL *ftl, lba_t lba, void *data, int size) { // 1. 分配新的物理页(Out-of-place Update) ppa_t new_ppa = allocate_page(ftl);
// 2. 写入数据到新物理页 nand_write(new_ppa, data, size);
// 3. 更新映射表 ppa_t old_ppa = ftl->mapping_table[lba]; ftl->mapping_table[lba] = new_ppa;
// 4. 标记旧页为无效 mark_page_invalid(ftl, old_ppa);
// 5. 检查是否需要 GC if (free_blocks(ftl) < ftl->gc.threshold) { garbage_collect(ftl); }}3.2 地址映射策略
| 映射策略 | 粒度 | DRAM 占用 | 性能 | 适用场景 |
|---|---|---|---|---|
| 页级映射 | 4KB 页 | 高(1TB SSD ~1GB) | 最好 | 随机 I/O 密集 |
| 块级映射 | 块(~1MB) | 低(1TB SSD ~2MB) | 一般 | 顺序 I/O 为主 |
| 混合映射 | 热数据页级,冷数据块级 | 中等 | 较好 | 通用场景 |
3.3 垃圾回收策略
# SSD 垃圾回收策略class GarbageCollector: def select_victim_block(self, blocks): """选择回收哪个块""" if self.policy == "greedy": # 贪心策略:选择无效页最多的块 return max(blocks, key=lambda b: b.invalid_pages / b.total_pages)
elif self.policy == "cost_benefit": # 成本效益策略:考虑块的使用频率和年龄 # cost_benefit = (1 - u) / (1 + u) * age # u = 有效页比例, age = 距上次擦除的时间 return max(blocks, key=lambda b: (1 - b.valid_ratio) / (1 + b.valid_ratio) * b.age)
elif self.policy == "d_choice": # d-choice:随机选 d 个块,选最优的 candidates = random.sample(blocks, min(self.d, len(blocks))) return max(candidates, key=lambda b: b.invalid_pages)
def gc_process(self, victim_block): """垃圾回收流程""" # 1. 读取有效页 valid_pages = [p for p in victim_block.pages if p.is_valid]
# 2. 将有效页搬移到新块 for page in valid_pages: new_ppa = self.allocate_page() self.nand_write(new_ppa, page.data) self.update_mapping(page.lba, new_ppa)
# 3. 擦除旧块 self.nand_erase(victim_block)
# 4. 更新统计 self.write_amplification += len(valid_pages)3.4 磨损均衡
磨损均衡(Wear Leveling)确保所有块的擦除次数均匀分布,避免某些块过早失效:
| 策略 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| 动态磨损均衡 | 只在写入时选择擦除次数少的块 | 简单 | 冷数据块不参与 |
| 静态磨损均衡 | 定期搬移冷数据,释放擦除次数少的块 | 均匀 | 额外写入开销 |
监控 SSD 健康状态的最佳工具是 smartctl -a /dev/nvme0n1,可以查看可用备用空间、已用寿命百分比、读写量等关键指标。结合 Prometheus 的 node_exporter + smartmon 脚本,可以实现 SSD 健康状态的自动告警。
四、SSD 接口与协议
4.1 SATA vs NVMe
| 特性 | SATA III | NVMe (PCIe 4.0) | NVMe (PCIe 5.0) |
|---|---|---|---|
| 带宽 | 600 MB/s | ~7 GB/s | ~14 GB/s |
| 队列深度 | 32 (NCQ) | 65,535 | 65,535 |
| 队列数 | 1 | 65,536 | 65,536 |
| 延迟 | ~100 μs | ~10 μs | ~5 μs |
| IOPS(4K 随机读) | ~100K | ~1M | ~2M |
| 协议开销 | 高(AHCI) | 低(NVMe) | 低(NVMe) |
# 查看 NVMe 设备信息nvme list
# 查看 NVMe 健康状态nvme smart-log /dev/nvme0
# 输出示例:# Critical Warning : 0# Temperature : 38°C# Available Spare : 100%# Percentage Used : 2% # 已用寿命百分比# Data Units Read : 12,345,678# Data Units Written : 23,456,789# Host Read Commands : 456,789,012# Host Write Commands : 345,678,901# Controller Busy Time : 1,234 hours# Power Cycles : 567# Power On Hours : 8,760# Unsafe Shutdowns : 12# Media and Data Integrity Errors : 04.2 NVMe 多队列架构
NVMe 的多队列架构消除了 SATA/AHCI 的单队列瓶颈:
- 提交队列(SQ):主机向控制器提交 I/O 命令
- 完成队列(CQ):控制器向主机通知 I/O 完成
- MSI-X 中断:多个中断向量,避免中断瓶颈
五、TRIM 与存储管理
5.1 TRIM 命令
TRIM 告诉 SSD 哪些逻辑块不再被使用,让 FTL 可以提前回收:
# 手动执行 TRIMfstrim -v /mnt/ssd
# 查看是否支持 TRIMlsblk -D# DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO# 0 512B 2G 0
# 持续 TRIM(挂载选项)mount -o discard /dev/nvme0n1p1 /mnt/ssd# 注意:持续 TRIM 可能影响性能,建议使用定期 fstrim
# 在 /etc/fstab 中配置# /dev/nvme0n1p1 /mnt/ssd ext4 defaults,noatime,discard 0 25.2 TRIM 对性能的影响
| 场景 | 无 TRIM | 有 TRIM |
|---|---|---|
| 全新 SSD | 性能最优 | 性能最优 |
| 使用 50% 后 | GC 开销增大,性能下降 | GC 高效,性能稳定 |
| 使用 90% 后 | 严重性能下降(50%+) | 性能基本稳定 |
| 删除大量文件后 | FTL 不知道块已释放 | FTL 立即回收 |
六、HDD vs SSD 对比
6.1 全面对比
| 维度 | HDD | SSD |
|---|---|---|
| 随机读 IOPS | ~100 | ~500K–2M |
| 随机写 IOPS | ~100 | ~100K–1M |
| 顺序读吞吐 | 100–250 MB/s | 3–14 GB/s |
| 延迟 | 5–15 ms | 10–100 μs |
| 每 GB 成本 | ~$0.03 | ~$0.3–0.5 |
| 功耗 | 5–10W | 1–5W |
| 噪音 | 有 | 无 |
| 抗震 | 差 | 好 |
| 写入放大 | 无 | 2–20x |
| 寿命限制 | 无(机械磨损) | 有(P/E 次数) |
| 数据恢复 | 较容易 | 极困难 |
6.2 数据库场景的存储选择
# 存储选择决策树def choose_storage(workload): if workload.type == "OLTP": if workload.iops_requirement > 10000: return "NVMe SSD (必选)" elif workload.iops_requirement > 1000: return "SATA SSD (推荐) 或 NVMe SSD" else: return "SATA SSD (够用)"
elif workload.type == "OLAP": if workload.data_size > "10TB": return "HDD + SSD 缓存 (成本优先)" else: return "NVMe SSD (性能优先)"
elif workload.type == "Archive": return "HDD 或对象存储 (成本优先)"
elif workload.type == "Log": return "NVMe SSD (低延迟写入)"七、实战:观察磁盘与 SSD
7.1 识别存储设备类型
# 查看块设备信息lsblk -o NAME,SIZE,TYPE,ROTA,TRAN,MODEL# ROTA=0 → SSD, ROTA=1 → HDD
# 查看详细设备信息smartctl -a /dev/nvme0n1smartctl -a /dev/sda
# 查看 SSD 寿命nvme smart-log /dev/nvme0n1 | grep "Percentage Used"7.2 I/O 性能测试
# 4K 随机读测试(SSD)fio --name=rand-read-4k --rw=randread --bs=4k \ --size=10G --numjobs=4 --iodepth=32 \ --ioengine=libaio --direct=1 --runtime=60
# 4K 随机写测试(SSD)fio --name=rand-write-4k --rw=randwrite --bs=4k \ --size=10G --numjobs=4 --iodepth=32 \ --ioengine=libaio --direct=1 --runtime=60
# 顺序读写测试fio --name=seq-read --rw=read --bs=128k \ --size=10G --numjobs=1 --ioengine=libaio --direct=1
# 混合读写测试(70% 读 / 30% 写)fio --name=mixed --rw=randrw --rwmixread=70 --bs=4k \ --size=10G --numjobs=4 --iodepth=32 \ --ioengine=libaio --direct=1 --runtime=607.3 观察写入放大
# 查看 SSD 写入统计nvme smart-log /dev/nvme0n1 | grep -E "Data Units|Host"
# 计算 WA# WA = Data Units Written / Host Write Commands * (512 / 页大小)# 例如:# Data Units Written: 1,000,000 (× 512B = 512GB)# Host Write Commands: 50,000,000 × 4KB = 200GB# WA = 512GB / 200GB = 2.56x7.4 MySQL 在不同存储上的表现
-- 在 HDD 和 SSD 上分别执行-- 插入 10 万条记录INSERT INTO storage_test (data, payload)SELECT CONCAT('record_', n), REPEAT('x', 1000)FROM ( WITH RECURSIVE nums AS ( SELECT 1 AS n UNION ALL SELECT n+1 FROM nums WHERE n < 100000 ) SELECT n FROM nums) t;
-- 随机点查SELECT * FROM storage_test WHERE id = 50000;
-- 范围扫描SELECT COUNT(*) FROM storage_test WHERE id BETWEEN 40000 AND 60000;| 操作 | HDD | SATA SSD | NVMe SSD |
|---|---|---|---|
| 插入 10 万行 | ~120s | ~15s | ~8s |
| 随机点查 | ~10ms | ~0.5ms | ~0.1ms |
| 范围扫描 2 万行 | ~500ms | ~50ms | ~20ms |
八、总结
| 主题 | 核心要点 | 关键词 |
|---|---|---|
| HDD | 机械运动决定 10ms 级延迟,寻道+旋转占 99.9%,顺序 I/O 比随机快数百倍 | 寻道, 旋转延迟 |
| NAND 闪存 | 页级读写、块级擦除约束导致写入放大,P/E 次数限制 SSD 寿命 | 擦除块, P/E |
| FTL | 逻辑到物理映射、垃圾回收、磨损均衡是 SSD 核心机制 | GC, 磨损均衡 |
| NVMe | 多队列架构消除 SATA 单队列瓶颈,延迟降低 10 倍 | 多队列, 低延迟 |
| 选型 | OLTP 选 NVMe SSD,OLAP 大数据量用 HDD+SSD 缓存,归档用 HDD/对象存储 | 场景选型 |
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






