🎯 什麼情境該想到我

當你在選資料庫/儲存引擎,或想理解「為什麼某個 DB 寫很快但讀較慢(或相反)」時。

⚙️ 怎麼用(依讀寫特性選)

  1. B-Tree(就地更新):資料原地排序更新,讀優化、範圍查詢好;寫入需隨機 I/O。傳統關聯式 DB(MySQL InnoDB…)。
  2. LSM-Tree(追加寫):寫入先進記憶體,再批次刷成不可變的 SSTable,背景合併(compaction)。寫優化、順序 I/O 快;讀要查多層 + 靠 Bloom filter 加速。RocksDB、Cassandra、LevelDB…
  3. 用「三種放大」判斷取捨:讀放大、寫放大、空間放大——沒有引擎三者全贏。寫重 → LSM;讀重/範圍查多 → B-Tree。

★ 核心對照(這張表就是全部)

B-Tree(PG、MySQL InnoDB、SQLite)LSM-Tree(RocksDB、Cassandra、LevelDB)
寫入★ 原地更新:找到頁 → 改 → 寫回(隨機寫)★ 只追加:寫 memtable → 滿了就刷成 SSTable(順序寫)
寫入吞吐中★ 高(順序寫快得多)
讀取★ 可預測: 次 I/O,走一條路徑⚠️ 可能查多層(memtable → L0 → L1 → …)
寫入放大中(頁分裂、WAL 寫兩次)★ 高(compaction 反覆重寫同樣的資料)
空間放大中(頁內碎片,約 60–70% 填充率)★ 低(壓縮率好)但 compaction 前會暫時翻倍
範圍掃描★ 好(葉節點有序且鏈結)中(要合併多層)
刪除就地標記★ 寫一個 tombstone(墓碑)——空間之後才回收
延遲穩定性★ 穩定⚠️ compaction 會造成尾延遲尖峰

★ 三種「放大」(理解儲存引擎的核心概念)

放大定義誰嚴重
寫入放大實際寫到磁碟的位元組 ÷ 應用寫入的位元組★ LSM(compaction 把同一筆資料重寫很多次)
讀取放大一次查詢要讀幾次磁碟★ LSM(要查多層)
空間放大實際佔用 ÷ 有效資料★ B-Tree(頁未填滿);LSM 在 compaction 時暫時很高

★ RUM 猜想:Read、Update、Memory 三者最多只能優化兩個。
這就是為什麼沒有「全面更好」的儲存引擎——每一個都是在這個三角上選了一個位置。

★ LSM 的讀取怎麼救

查一個 key:
  ① memtable(記憶體,快)
  ② ★ Bloom filter 判斷「這個 SSTable 可能有嗎」
     → 說「沒有」就是真的沒有(不會偽陰性),直接跳過,不用讀磁碟
  ③ 真的可能有才讀 SSTable

★ Bloom filter 是 LSM 讀取效能的關鍵。沒有它,每次讀都要查所有層。
(它有偽陽性但沒有偽陰性——這個不對稱正是它有用的原因。)

★ Compaction:LSM 的命脈,也是它的痛點

策略做法取捨
Size-tiered同樣大小的 SSTable 合併成更大的★ 寫入放大低,空間放大高(同一筆資料存在多層)
Leveled每層大小固定倍增,層內 key 不重疊★ 讀取與空間好,寫入放大高
混合 / 時間序列專用Cassandra 的 TWCS(依時間窗)時序資料專用

★ Compaction 是背景 I/O,會和前台查詢搶資源 →
這就是 LSM 尾延遲不穩定的根源。 生產上要限制 compaction 的頻寬。

★ 實務上什麼時候該在意這件事

多數時候不用在意——你不會因為儲存引擎而選資料庫。但這幾個情況要:

情況影響
寫入吞吐是瓶頸(日誌、遙測、IoT、事件流)★ LSM 明顯有利
需要穩定的 p99 讀取延遲★ B-Tree(LSM 的 compaction 會製造尖峰)
大量範圍掃描 / 分析查詢B-Tree 或列式儲存
★ 寫入模式是「大量刪除或覆寫」⚠️ LSM 的 tombstone 會累積;Cassandra 的「墓碑地獄」是經典問題
SSD 壽命敏感(寫入放大 = 磨損)要算寫入放大

★ 一個實用的觀察:
PostgreSQL 的 MVCC 讓 UPDATE 實際上是「寫新版本 + 標記舊版本」——
行為上有點像 LSM(所以需要 VACUUM 來回收,就像 compaction)。
★ PG 的表膨脹(bloat)與 autovacuum 調校,本質上就是 compaction 的問題。

其他儲存形態

形態適合
列式(columnar)★ 分析查詢(只讀需要的欄、壓縮率極高)——ClickHouse、Parquet
雜湊索引只需要點查、資料能放記憶體(見 雜湊表)
B+Tree vs B-Tree★ B+Tree 只在葉節點存資料且葉節點互相鏈結 → 範圍掃描快得多,實務上都是 B+Tree

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意 / 什麼時候不適用

  • ★ 不要為了儲存引擎換資料庫。生態、運維成熟度、團隊熟悉度的權重遠高於此。
    這個知識的用途是「解釋為什麼我的寫入會慢 / p99 會抖」,不是選型的第一準則。
  • ★ LSM 的 tombstone 會咬人。大量刪除後空間不會立刻回收,而且讀取時要掃過墓碑。
    Cassandra 的 gc_grace_seconds 與墓碑閾值是必須理解的營運參數。
  • ★ PG 的 autovacuum 調校是同一類問題。高更新率的表會膨脹 → 查詢變慢 → 要調 vacuum 的積極度。
  • B-Tree 的頁分裂在隨機 key 插入時最嚴重。★ 用遞增的主鍵(如 UUID v7 而非 v4)可以大幅減少分裂與隨機 I/O。
  • 索引不是免費的。每個索引都要在寫入時更新 → 寫入放大乘以索引數量。
  • 量測的是「你的工作負載」。benchmark 數字與你的讀寫比、key 分布、資料大小高度相關。
  • 雲端資料庫的儲存層可能完全不同(Aurora 把 log 直接寫進分散式儲存),上面的模型不完全適用。

🔗 相關工具