資料與儲存 2026 年 6 月 7 日

2026-06-07 — SQLite UUID 主鍵 B-Tree 碎片化、Redis 8.8 Array 型別與 INCREX

primary=https://andersmurphy.com/2026/06/05/the-perils-of-uuid-primary-keys-in-sqlite.html primary=https://redis.io/blog/announcing-redis-8-8/ primary=https://redis.io/blog/diving-deep-into-rediss-new-array-data-type/

SQLite UUID 主鍵的效能陷阱:B-Tree 碎片化與寫入放大

andersmurphy.com · 2026-06-05

一篇在 Lobsters 獲得高關注的技術文章詳細分析在 SQLite 中使用 UUID 作為主鍵的效能代價,結論是 UUID v4 作為主鍵會導致 B-Tree 結構嚴重碎片化,在寫入密集場景下產生顯著的效能退化,作者以 Clojure + SQLite 應用為背景提供了具體測量數據與解決方案。

問題根源:隨機插入導致 B-Tree 分裂

SQLite 的主鍵索引是一棵 B-Tree,頁面大小預設 4 KB。UUID v4 是隨機生成的 128 位元值,新記錄的主鍵幾乎必然落在已存在記錄的隨機位置,觸發 B-Tree 中間頁的分裂與重新平衡。對比之下,自增整數(ROWID)永遠追加到 B-Tree 最右側葉子節點,不觸發已有頁面的修改。

隨機插入的直接後果有三:(1)每次插入平均需要讀取並寫回更多 B-Tree 頁面(寫入放大);(2)頁面填充率下降(B-Tree 分裂後兩個子節點各約半滿),導致相同資料量需要更多頁面,增加讀取時的 I/O;(3)SQLite 的 WAL(Write-Ahead Log)需要記錄更多頁面修改,checkpoint 時的同步工作增加。

測量結果

文章以 100 萬筆記錄的插入測試比較:

  • ROWID(自增整數):插入耗時基準值
  • UUID v4:插入耗時約 ROWID 的 4–6 倍,資料庫文件大小約 ROWID 的 1.5–2 倍(頁面碎片化)
  • UUID v7(時間排序 UUID):插入耗時接近 ROWID,因為 UUID v7 的高位元是 Unix 毫秒時間戳記,保證新值大於既有值,不觸發隨機分裂

解決方案

UUID v7 是在需要全域唯一識別符的情況下,保留 SQLite 插入效能的最佳選擇。UUID v7 格式(RFC 9562)的前 48 位元為 Unix 毫秒時間戳記,後 74 位元為隨機值,保證在相同毫秒內多個節點生成的 UUID 幾乎嚴格遞增(偶爾的毫秒衝突由隨機位元部分緩解),讓 B-Tree 退化為近似追加模式。對於不需要跨系統唯一性的場景,SQLite 原生的 INTEGER PRIMARY KEY AUTOINCREMENT 仍是效能最優選項。

原始來源:andersmurphy.com


Redis 8.8:Array 資料型別、INCREX 限速器與 Stream 效能改進

Redis Blog · 2026-06-04

Redis 發布 8.8 版本,引入三個主要新特性:原生 Array 資料型別、INCREX 帶過期時間的增量指令,以及 XNACK 的 Stream pending entry 批次確認。8.8 延續 Redis 8.x 系列的高效能方向,同時提升 MGET/MSET 與 Stream 操作的吞吐量。

Array 資料型別

Redis 新增 ARRAY 型別,支援有序、支援下標存取的可變長序列,區別於現有的 List(雙向鏈結串列,O(n) 隨機存取)。Array 型別使用緊湊的連續記憶體佈局,提供 O(1) 下標讀寫,代價是插入(非追加端)仍為 O(n) 移位。新指令集:

  • ASET key index value:設定指定下標的元素
  • AGET key index:讀取指定下標的元素
  • APUSH key value [value ...]:追加元素
  • ALEN key:回傳長度
  • ARANGE key start stop:範圍讀取(類似 LRANGE)

INCREX:帶過期時間的原子增量

INCREX key increment milliseconds 在單一原子操作中執行增量並設定毫秒級過期時間,解決限速器(rate limiter)常見的 INCR + PEXPIRE 兩步驟競態條件。傳統做法使用 Lua 腳本保證原子性,INCREX 將此模式提升為原生指令,消除腳本呼叫的開銷與維護負擔。

效能改進

8.8 的效能改進集中在兩個面向:MGET/MSET 在大批次下的吞吐量提升——透過 pipeline 批次路徑最佳化,減少每個鍵的平均處理指令數;以及 Stream 消費者群組(consumer group)的 XACK 批次處理——XNACK 指令允許在一次呼叫中確認多個 pending entry,降低高吞吐 Stream 消費場景的往返次數。基準測試顯示在 100 個鍵的 MGET 批次上有 15–23% 的吞吐量提升。

原始來源:Redis BlogArray 資料型別深入解析


End of article
0
Would love your thoughts, please comment.x
()
x