UUID 與 ULID 是分散式系統與資料庫中用來發號「唯一 ID」的機制。它們形式相近,但在標準化與否、儲存效率、人類可讀性、資訊暴露風險等方面皆有差異。本文聚焦於最具代表性的三種方式——UUID v4UUID v7ULID——梳理特徵與選型指引,最後補充 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) 儲存。
  • MySQL/InnoDB
    • BINARY(16) 對 v4/v7 而言高速且省空間。v7 按 大端序 儲存後 memcmp 即對應時序。ULID 可用 CHAR(26)BINARY(16)+轉換。
  • SQLite
    • 無專用 UUID 型別。可使用 BLOB(16)TEXT。加上索引的 BLOB 效率相對良好。

安全觀點的補強

  • 抗推測性: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 或其他方案。