44.5 存储引擎:读写放大与空间放大
一句话
存储引擎的好坏最终都落到三个放大系数上:写放大(写 1 字节实际写了几字节)、读放大(读 1 行实际读了几行/几页)、空间放大(1GB 数据实际占几 GB)——优化引擎,本质是把这三个数往 1 压,而它们互相牵制。
三个放大系数的来源
| 放大 | 定义 | 主要来源 | B+ 树 | LSM |
|---|---|---|---|---|
| 写放大 WA | 实际写字节 / 逻辑写字节 | 页分裂、compaction 重写、日志 | 低(1–2×) | 高(10–30×) |
| 读放大 RA | 实际读的行/页 / 需要的数据 | 多层查找、页内多余列、块过大 | 低 | 中高(查多层) |
| 空间放大 SA | 实际占用 / 逻辑数据 | 多版本、tombstone、页内空隙 | 低 | 中(待合并文件) |
实测:LSM 的写放大与布隆过滤器的读收益
模拟 5 万条记录、内存表 1000 条、fanout=4:
| 指标 | 结果 |
|---|---|
| 写放大 | 约 3.0×(每层合并都重写数据) |
| 生成文件数 | 62 个 SSTable |
| 5000 次点读(无布隆) | 读 7462 个文件 |
| 5000 次点读(有布隆) | 读 5000 个文件,跳过 2521 次(33%) |
注意这个 WA=3× 是 “小规模、少层” 的乐观结果:真实 LSM 每层都要重写,层数越多 WA 越高(常见 10–30×)。层数 = log_fanout(数据量/内存表),所以 “fanout 越大、层越少、WA 越低,但单次合并越贵”。
LSM 写放大 ≈ fanout × 层数
例:fanout=10、数据 1TB、memtable 256MB → 层数 ≈ 4 → WA ≈ 40×(量级估算)
实测:B+ 树的写路径
同一份 5 万条记录,sqlite3(B+ 树)写入 86ms、文件 5.4MB:写放大接近 1(就地改页),但代价是随机写与页分裂——机械盘上会明显变慢。
反模式
| 反模式 | 后果 |
|---|---|
| 只看 “写入快”,不看 WA | SSD 寿命与 IO 被 compaction 吃光 |
| 用大 fanout 压 WA | 单次 compaction 搬运巨大,延迟尖刺 |
| LSM 不设布隆过滤器 | RA 高,点查慢 |
| B+ 树频繁随机更新大表 | 页分裂 + 随机 IO,写放大上升 |
| 不监控 SA | 磁盘 “莫名” 涨到告警,其实是旧版本堆积 |
| 用 “平均延迟” 评估 compaction 影响 | 尖峰被平均掩盖,用户感知不到 |
三条铁律
🔑 三个放大互相牵制:压 WA 常常抬 SA,压 RA 常常抬内存。
🔑 LSM 的 WA 随层数线性增长;层数由 “数据量/内存表 × fanout” 决定。
🔑 放大系数要监控,而不是等磁盘满/IO 饱和才发现。
自检
Q1 为什么 LSM 的写放大比 B+ 树高这么多?
▸ 答案:因为 LSM 的数据会被反复重写:从 memtable 刷到 L0,再逐层合并到 L1、L2…每合并一次就重写一遍。B+ 树只在页分裂时搬少量数据。所以 LSM 用 “多写几倍” 换 “顺序写 + 高吞吐”。
Q2 增加 fanout 为什么能降低写放大?
▸ 答案:因为 fanout 越大、层数越少,每条记录被重写的次数越少(WA ≈ fanout × 层数)。但合并时的单次工作量更大(一次合并 fanout 个文件),会造成更大的 IO 尖峰与内存占用——这是 “平均 WA 下降,尾部延迟上升” 的典型权衡。
Q3 空间放大在 LSM 里具体是什么?
▸ 答案:三部分:多版本文件(同一 key 在多层存在)、tombstone(删除标记要保留到最底层合并)、待合并的临时数据。所以 “逻辑 100GB” 可能占用 150–200GB,直到 compaction 完成。
Q4 为什么 “布隆过滤器只省 33%” 也值得做?
▸ 答案:因为它省的是随机 IO:33% 的文件读取意味着 33% 的 IOPS 与延迟下降,而布隆过滤器本身只有几 bit/key 的内存开销、查询是 O(1) 哈希。对点查型负载,这是 “几乎免费” 的优化。
Q5 B+ 树的写放大在什么情况下会变高?
▸ 答案:随机更新大表时:每次更新都要读页、改页、可能分裂,写放大与随机 IO 一起上升;加上 WAL 的日志写入,实际 WA 可能是 2–3×。顺序插入(自增主键)则接近 1×。
Q6 如何 “监控” 这三个放大系数?
▸ 答案:WA:对比 “逻辑写入量” 与 “磁盘写字节”(/proc/diskstats 或云监控);RA:对比 “返回行数” 与 “扫描行数”(执行计划/慢日志);SA:对比 “逻辑数据量” 与 “磁盘占用”(表大小 vs 行数 × 行宽)。三者都有现成指标,关键是要 “定期看趋势”。
Q7 什么时候该接受 “高写放大”?
▸ 答案:当写入吞吐与顺序性比 IO 成本更重要时:日志、时序、消息存储等 “追加为主” 的场景,LSM 的 WA 可以用 “更大内存表 + 更少层” 压下来,换来远高于 B+ 树的写吞吐。反之,更新密集、容量敏感的场景应选 B+ 树或 “LSM + 定期重建”。
关联
- 前置:44.1 存储引擎:B+ 树与 LSM 树
- 并列:44.2 存储引擎:WAL 与崩溃恢复、44.3 存储引擎:缓冲池与页面管理
- 下游:44.4 存储引擎:压缩与编码、44.16 OLAP 与列式存储