UUID v4 / UUID v7 / ULID 全面比較:如何在時序性、效率與可讀性之間取捨
UUID 與 ULID 是分散式系統與資料庫中用來發號「唯一 ID」的機制。它們形式相近,但在標準化與否、儲存效率、人類可讀性、資訊暴露風險等方面皆有差異。本文聚焦於最具代表性的三種方式——UUID v4、UUID v7、ULID——梳理特徵與選型指引,最後補充 UUID v1 / v6 的概要。
以下是可在瀏覽器內完成 ID 生成與時間資訊還原的工具:
UUID v4:完全隨機型
UUID v4 在 128bit 中有 122bit 作為亂數,以極低的碰撞機率為優勢。相對地,它無法依時間順序排列,在 B-Tree 類索引中會頻繁觸發頁分裂,插入局部性容易惡化。適用於工作階段 ID 或一次性金鑰等「完全隨機即可」的用途。
UUID v7:可按時間排序的正統進化
UUID v7 於 2024 年寫入 RFC 9562,前 48bit 儲存 UNIX 紀元的毫秒時間戳,其餘部分以亂數填充。字典序(字串比較)即可對應生成順序(時序),因此即使用作主鍵,索引局部性仍然良好。此外可直接存入既有的 uuid
型別或 BINARY(16)
,也是一大優勢。
- 亂數位長:除去內部 version/variant 的若干位元,v7 約有 74bit 左右亂數。實作上可能透過單調生成(避免同一毫秒內碰撞)微調部分亂數位,但統計上的隨機性已足夠。
- 資訊暴露風險:可從 ID 中推算生成時間(毫秒精度)。若發號量巨大且亂數實作薄弱,推測鄰近 ID 的可能性相對提高。內部系統通常能接受,但若公開端點不希望暴露近似連號,就必須留意。
ULID:更易讀的人性化 ID
ULID 於 2016 年提出,屬於事實標準,以 Crockford Base32 的 26 個字元 表示。它排除容易混淆的字元(O/I/L/1),便於嵌入 URL 或複製。同 UUID v7 一樣,前 48bit 含有毫秒級時間戳,因此字串字典序就等同時序。
- 排序保證:ULID Base32 字串之間的字典序比較永遠等同時間順序(單調生成規格也已廣泛普及)。
- 儲存形態:TEXT(26) 便於直接閱讀;若要在資料庫中以二進位儲存,需進行 Base32↔16B 轉換。雖可視為 128bit 寬的二進位資料,但與 RDB 的 UUID 專用型別往往不相容。
- 資訊暴露風險:與 v7 相同,會揭露毫秒級時間。若作為公開 API 的資源 ID 而不希望暴露發號順序,需審慎評估。
三種方式的對照表(實務觀點)
項目 | UUID v4 | UUID v7 | ULID |
---|---|---|---|
標準化 | RFC 4122 | RFC 9562(2024) | 非標準(事實標準) |
位元構成 | 128bit 中 122bit 亂數 | 48bit=UNIX ms + 餘下亂數(可單調生成) |
48bit=UNIX ms + 80bit 亂數 |
亂數熵 | ≈122bit | ≈74bit(扣除 version/variant 的估算值) | 80bit |
字串表記 | 36 字元(十六進位+連字號) | 36 字元(十六進位+連字號) | 26 字元(Crockford Base32) |
自然排序(文字) | ×(隨機) | ○(字典序=時序) | ○(字典序=時序) |
自然排序(位元) | × | ○(BINARY(16) 的 memcmp 升序) |
○(此前綴 6B 時間為大端時 memcmp 升序) |
資料庫儲存特性 | uuid 型 / BINARY(16) 最佳 |
uuid 型 / BINARY(16) 最佳 |
TEXT(26) 可讀性高,BINARY(16) 需轉換 / 多半與 UUID 型別不相容 |
索引局部性 | 差(隨機插入致頁分裂) | 佳(尾端追加/需注意熱點) | 佳(尾端追加/需注意熱點) |
等值查詢成本 | 低(16B 比較) | 低(16B 比較) | TEXT 稍高(受 collation 影響)/BINARY 較低 |
編碼負擔 | 十六進位↔16B(輕量) | 十六進位↔16B(輕量) | Base32↔16B(稍重) |
可讀性/URL 適性 | 低 | 低 | 高 |
資訊暴露(生成時間) | 無 | 有(毫秒) | 有(毫秒) |
主要用途 | 工作階段 ID、一次性金鑰 | 資料庫主鍵、事件 ID、日誌時序 | URL・公開 ID、日誌可視化 |
補充(v7/ULID 的熱點問題):若針對單一分片或單一主節點集中於尾端插入,B-Tree 尾端可能過熱。可考慮 上位位元的小幅洗牌(“prefix shuffle”) 或搭配 分片鍵。
資料庫實作備忘(PostgreSQL / MySQL / SQLite)
- PostgreSQL
- v4/v7:以
uuid
型別最合適。v7 以字串寫入uuid
後即可享有排序特性。 - ULID:使用
char(26)
/text
,或轉換為bytea(16)
儲存。
- v4/v7:以
- MySQL/InnoDB
BINARY(16)
對 v4/v7 而言高速且省空間。v7 按 大端序 儲存後 memcmp 即對應時序。ULID 可用CHAR(26)
或BINARY(16)
+轉換。
- SQLite
- 無專用 UUID 型別。可使用
BLOB(16)
或TEXT
。加上索引的 BLOB 效率相對良好。
- 無專用 UUID 型別。可使用
安全觀點的補強
- 抗推測性:v7/ULID 的時間位元相同,若在同一毫秒內大量發號且亂數實作薄弱,推測鄰近 ID 的風險相對上升。務必採用作業系統提供的密碼學安全 PRNG,並盡量降低單調增量的偏斜。
- 中介資料洩漏:可從 ID 中推估生成時間與大致發號量。公開 API 建議採用內部 ID 與外部公開 ID 分離的設計。
總結(選型指引)
- UUID v4:完全隨機。缺乏排序能力。理論上仍有極小的碰撞風險。適合工作階段 ID 或 CSRF Token 等用途。
- UUID v7:標準化的有序 UUID。最適合內部系統與資料庫主鍵。透過單調生成,可在實務上避免碰撞。
- ULID:相對更「人性化」,易於閱讀。若重視對外呈現或日誌可視化,是強力候選;但作為內部主鍵通常不及 v7。透過單調生成,可在實務上避免碰撞。
補充:UUID v1 / v6(簡述)
-
UUID v1(時間+ MAC) 含有 60bit 時間戳(100ns 精度)+節點識別子(多為 MAC 位址)。具備良好時序性,但因含有 MAC 資訊,存在洩露主機資訊之虞,且在時鐘回撥時需要留意實作。現今多以隱私疑慮為由不再推薦。
-
UUID v6(v1 的重新排序版) 將 v1 的時間戳重新排成大端序以提升字典序排序能力。歷史上曾被視為「更易排序的 v1」,但標準化最終收斂至 v7。新設計通常直接選擇 v7 而非 v6。
附錄:實作檢查清單
- 亂數必須使用密碼學等級 PRNG(作業系統提供的安全來源)。
- v7/ULID 的單調生成須同時避免同毫秒碰撞並抑制亂數偏斜。
- 資料庫優先採用
uuid
型 /BINARY(16)
,以免字串欄位 collation 造成效能下降。 - 明確訂定對外 ID 的策略(是否允許曝露順序)。必要時區分內部 ID 與外部 ID。
總結而言,目前在技術面上內部主鍵首選 UUID v7;若更重視可讀性或有技術限制,ULID 亦是備選。僅在禁止暴露時間戳等特殊需求下,才會改採 v4 或其他方案。