一次 4 KB 的随机读要 12.69 毫秒,折算下来只有 79 次/秒、0.31 MB/秒——而顺序读能到 150 MB/秒,差 487 倍。差的钱全花在【等】上:等磁头移过去,等扇区转过来。
前七章讲的是【怎么管理已有的东西】,这一章回到硬件本身:外部存储器到底是怎么读写的,为什么它慢得这么顽固。
一、盘片、磁道、扇区
一块机械硬盘,拆开就是一个转盘加了根针。 盘片:一张圆形的磁性涂层玻璃或铝板,数据就记在表面那层涂层上。一个盘里可能有好几片,叠在一起转。 磁道:盘面上的一圈同心圆。数据沿磁道排,一磁道一磁道往外扩。几万到几十万条磁道,每条都很窄。 扇区:磁道上一小段弧。传统是 512 字节,现在基本是 4 KB。它是【读写的最小单位】——要读 1 个字节,也得把整个扇区读出来。 磁头:悬在盘面上方飞的那一小块,它负责把磁场的变化变成电信号。它不接触盘面——一挨上去就是一次划伤。 机械臂:把磁头送到目标磁道上的那根杆。它是整块硬盘里唯一【会动】的机械部件,也是所有延迟的来源。 一句话:这个结构决定了,硬盘不是【想读哪里就读哪里】,而是【得先把针挪过去】。
二、一次访问的三笔开销
每一次读写,都要付三笔钱。 ① 寻道:把磁头移到目标磁道。这一笔最贵,因为要推动整个机械臂——典型的平均寻道时间是 4 到 10 毫秒。取 8.5 毫秒。 ② 旋转等待:等目标扇区转到磁头下面。平均要等半圈。7200 转/分 = 一圈 8.33 毫秒,半圈 4.17 毫秒。 ③ 传输:数据真正从盘面读出来。这一笔只和字节数有关——典型持续传输率 150 MB/秒,4 KB 只要 0.03 毫秒。 三笔相加:8.5 + 4.17 + 0.03 = 12.69 毫秒。 关键在比例:传输只占 0.2%,剩下 99.8% 全在【等机械臂和等盘子转】。这是理解这一整章的唯一前提。
三、三笔钱谁最贵
Demo 78 把四种访问放在一张表里。结论很清楚:小数据量的时候,前两笔吃掉了一切。 随机读 4 KB:12.69 毫秒,其中 12.66 是【等】。 顺序读 8 MB:66 毫秒,其中 53.33 是传输——因为一次寻道之后,后面 8 MB 都在磁头下面顺序流过去,寻道与旋转只付了一次,被摊薄了。 反过来算一遍就明白了:4 KB 要 12.69 毫秒,那一秒最多做 79 次,也就是 79 次 × 4 KB = 0.31 MB/秒。 同一个盘,同一根针,换个访问方式,带宽从 0.31 变成 150。487 倍的差距,不是硬件换了,是【用法】换了。
四、为什么数据库一门心思要顺序读
因为顺序读直接把三笔开销里的两笔砍掉了。剩下的那一笔(传输)本来就是唯一【有必要】花的钱。 这也解释了一堆看起来莫名其妙的工程手段: B+ 树把叶子节点连起来:为了让范围扫描能顺序走。 日志(WAL)与 LSM 树:把随机写改成顺序追加(7.5 讲过)。 预读窗口:赌你接下来还要读相邻的块(8.9 会讲)。 批量提交:攒够一批一起写,让它们相邻。 这些手段全都是同一招:把【很多次各自付寻道的访问】变成【一次付费的连续流】。 所以【顺序比随机快】这句话,在机械盘上不是优化建议,是物理事实。而在 SSD 上它就不成立了(8.11)。
五、动手 Demo:HDD 的数据读写机制——一次访问的钱花在哪
这个 Demo 不测真盘(沙箱里没有机械盘,8.3 会说这件事),它按真盘的参数把三笔开销算出来,看它们在一个 4 KB 请求里各占多少。 参数取自一块普通 7200 转台式机盘:平均寻道 8.5 毫秒,持续传输 150 MB/秒。
RPM = 7200
SEEK = 8.5 # 毫秒
XFER = 150.0 # MB/秒
rot = 60000.0 / RPM / 2.0 # 半圈,毫秒
rows = []
for name, size in (("随机读 4 KB", 4 * 1024),
("随机读 256 KB", 256 * 1024),
("顺序读 1 MB", 1024 * 1024),
("顺序读 8 MB", 8 * 1024 * 1024)):
xf = size / (XFER * 1024 * 1024) * 1000
tot = SEEK + rot + xf
rows.append((name, size, SEEK, rot, xf, tot))
for name, size, sk, rt, xf, tot in rows:
print("%-14s 寻道 %.2f 旋转 %.2f 传输 %6.2f 合计 %6.2f"
% (name, sk, rt, xf, tot))
tot4 = rows[0][5]
print("4 KB 随机 IOPS = %.0f" % (1000.0 / tot4))
print("4 KB 随机带宽 = %.2f MB/秒" % (4.0 / 1024 / (tot4 / 1000)))
print("与顺序读的差距 = %.0f 倍" % (XFER / (4.0 / 1024 / (tot4 / 1000))))
实跑输出:
--------------------------------------------------------------------
HDD 的数据读写机制——一次访问的钱花在哪
--------------------------------------------------------------------
① 一次访问的三笔开销
**① 寻道**:**把磁头移到目标磁道。****这一笔最贵,因为要推动整个机械臂。**
**② 旋转等待**:**等目标扇区转到磁头下面。****平均要等半圈。**
**③ 传输**:**数据真正从盘面读出来。**
参数 取值 说明
-------------------------------------------------
转速 7200 rpm 每分钟转多少圈
平均寻道 8.5 毫秒 机械臂移动的代价
平均旋转 4.17 毫秒 半圈的时间
持续传输 150 MB/秒 磁头下经过的字节流
② 两种访问,时间花在完全不同的一笔上
访问 读多少 寻道 旋转 传输 合计
-----------------------------------------------------------------------
随机读 4 KB 4 KB 8.5 4.17 0.03 12.69 毫秒
随机读 256 KB 256 KB 8.5 4.17 1.67 14.33 毫秒
顺序读 1 MB 1 MB 8.5 4.17 6.67 19.33 毫秒
顺序读 8 MB 8 MB 8.5 4.17 53.33 66.00 毫秒
**看最后两行**:**顺序读 8 MB 时,传输占了大头**,**寻道与旋转被摊薄了。**
**看前两行**:**随机读 4 KB 时,传输只占 0.03 毫秒**,**剩下 12.7 毫秒全花在【等】上。**
**换算一下**:**随机 4 KB 最多 79 次/秒(IOPS)。**
**折算带宽只有 0.31 MB/秒**——**而顺序读能到 150 MB/秒。**
**差 487 倍。**
**这就是为什么【数据库要尽量顺序读】这件事这么重要**——
**不是算法层面的优化,是直接把三笔开销里的两笔砍掉。**
📋 **Linux 上看真实参数**:
`sudo hdparm -I /dev/sda | grep -i "nominal media rotation"`;
`sudo hdparm -tT /dev/sda` 测缓存与盘的持续读速度。
前三列是【模型】不是实测——沙箱里没有机械盘,这几个数是拿真盘的典型参数算的。 看随机读 4 KB 那一行:传输只占 0.03 毫秒,剩下 12.66 毫秒全花在等磁头和等盘子转上。 再看顺序读 8 MB:传输变成 53.33 毫秒,成为大头——因为一次寻道之后,后面 8 MB 全在磁头下顺流而过。 所以【顺序比随机快 487 倍】不是软件优化出来的,是【同一块盘换个用法】自然的结果。
📋 Linux 上看真实参数:sudo hdparm -I /dev/sda | grep -i "nominal media rotation";
📋 sudo hdparm -tT /dev/sda 测缓存与盘的持续读速度。
30 秒实验
- 算一遍:如果寻道从 8.5 毫秒降到 4 毫秒,4 KB 随机的带宽能从 0.31 涨到多少?
grep . /sys/block/sda/queue/rotational,1 表示机械盘,0 表示固态。- 想一想:为什么 1 个字节和 4 KB 的随机读耗时几乎一样?
常见误区
- 【硬盘按字节读,所以读 1 个字节比读 4 KB 快很多】。最小单位是扇区(现在 4 KB),读 1 个字节也要把整个扇区读出来。
- 【转速越快硬盘就越快】。转速只影响旋转等待这一笔,而它在总时间里只占三分之一左右(8.2 会算)。
- 【顺序读快是因为硬盘有缓存】。顺序读快是因为寻道只付一次,缓存是另一回事(那是 8.9 的预读与第 6 章的页缓存)。
自测三问(点开对答案)
1. 为什么 4 KB 随机读的带宽只有 0.31 MB/秒,比顺序读低 487 倍? —— **因为每次都要重新付一次寻道与旋转**:**12.69 毫秒里 12.66 是等待**,**只有 0.03 毫秒在真搬数据。****顺序读把这些等待摊到几 MB 上,所以几乎只剩传输。** 2. 为什么读 1 个字节和读 4 KB 的随机读耗时差不多? —— **因为读写的最小单位是扇区(4 KB)**,**读一个字节也要整扇区经过磁头。****时间由【机械动作】决定,与你要多少字节无关。** 3. 数据库为什么要费那么大劲把随机写改成顺序写? —— **因为在机械盘上,随机与顺序差 400 倍以上**,**而这个差距全部来自【多付的寻道与旋转】。****日志、LSM 树、批量提交,做的都是同一件事:把多次付费变成一次付费。**- ☐ 8.2 HDD 的性能特性
相关笔记
- HDD的性能特性 — 转速能救多少
- 随机访问 — 同一个模型换个走法
- 页面缓存 — 挡在硬盘前面的那一层
- 🐧 Linux是怎样工作的 · 索引