
圖片來源:ChatGPT 生成
本想說,《閒談軟體設計 2014 ~ 2024》裡已經談了三回的 UUID,應該不會再有機會談到 UUID,作為我常用的資料庫主鍵,這麼多年過去了,難道沒有任何新意嗎?趁著系統要加入一個新模組,我稍微研究了一下,沒想到還真的有,UUID 家族新增了 v6 和 v7,還有一個同樣是 128-bits 長度的 ULID,今天就聊聊這兩個新玩意吧。
UUID v6 & v7
第一篇聊 UUID,是寫在 2017 年,當時的標準規範是 RFC 4122,此標準中 UUID 有五個版本,只有第一個版本算是 Time-based,但因為時間戳記的位元分布有點怪,導致無法滿足依時間排序,每隔一段時間,就會因為低位元重置導致排序亂掉。
所以在第一篇討論 UUID 時,就有提過,透過調整時間戳記的位元分布,是可以產生能按時間排序的 UUID。這個概念正好就是 UUID v6 和 v7 的核心概念,UUID v6 和 v7 是在 2020 的 RFC 9562 中提出,在 2.1 節中,條列了幾項為什麼要提出 RFC 9562 來擴充 RFC 4122 (僅列與本文相關的幾項):- 既有的 UUID 無法按時間排序 (v3、v4 和 v5)
- 每 100 奈秒的計數作為時間戳記不常見也不容易用 IEEE 754 呈現 (v1)
- 需要解析才能按時間排序 (v1)
- 未明確區分「產生 UUID」與「儲存 UUID」的不同需求
所以 UUID v6 和 v7 到底和 v1 哪裡不一樣?直接看 128-bits 的內容配置是最清楚的,UUID v1 把時間戳記的最低 32 bits 擺在最高位 (這是無法按時間排序的主要原因)。

UUID v1 Layout (擷取自 RFC 9562)
因此,UUID v6 提供一個幾乎與 v1 幾乎一樣,唯一差別是把時間戳記的最高 32 bits 擺在最高位,也就不會有每隔一陣子順序就亂掉的問題。

UUID v6 Layout (擷取自 RFC 9562)
而 UUID v7 則是只使用 48-bits 的時間戳記,而且是直接使用毫秒,不再是每 100 奈秒的計數,更重要的是,時間戳記不再拆成三段,讓解讀上更容易,除了時間戳記、版本和變體需要的位元外,其他都是亂數,不再使用任何可識別主機位置的資訊,提高了安全性。

UUID v7 Layout (擷取自 RFC 9562)
從上述的配置可以看到,UUID v6 和 v7 就是希望讓 UUID 能夠按時間排序。
除此之外,UUID 產生出來,很大的機會是要儲存的,目前很多資料庫中,UUID 已經被當成主鍵的常用選項之一,而 UUID v6 和 v7 的設計相較於完全隨機的 UUID v4 來說,資料庫的索引 (index) 是有機會最佳化的,讓時間戳記相近的 UUID 可以存在 B-Tree 同一個節點中,在磁碟讀取時只需要一次操作就可以取得相近的 UUID,能增加查詢的效率。
ULID
既然新版的 UUID 這麼好,就通通改成用新版的?這倒是不一定,有幾個情況要觀察一下,首先是程式語言的支持,Java 25 還沒有內建對 v6 及 v7 的支援,必須用第三方套件;另外,資料庫是否能正確處理新版的 UUID?最後就是 UUID 真的很長,十六進制的表示法,加上連字號,36 個字母,在很多場景下真的很難使用,也因此在第二篇聊 UUID 的文章中,透過 Base 32 編碼讓 UUID 變短一點。
RFC 9562 的 2.1 節中提到,在制訂新 UUID 標準中,參考 16 種既有技術,其中包含了 ULID。ULID 的全名是 Universally Unique Lexicographically Sortable Identifier,從名稱上就可以看得出意圖:可以按字典排序,或是換個說法,可以用字串排序。

ULID Layout (擷取自官網)
從 ULID 的配置可以看的出來,長度依然是 1288-bits,最高的 48 bits 是時間戳記,剩餘的 80 bits 都是亂數,但採用 Base 32 編碼,只能說英雄所見略同 (自己說的 XD),因此產生出來的 ID 長度是 26 個字元,雖然不是最短的,但沒有特殊符號,且不分大小寫 (Base 64 會有特殊符號且分大小寫),作為網址的一部分是安全的。
魔術數字 48
不知道有沒有人注意到,UUID v6 與 v7,以及 ULID 在時間戳記的長度都只取 48 bits 呢?答案其實很簡單,以寫文章的當下 2025-12-13 12:50:30 (台北時間),時間戳記是 1,765,601,430,000 ms,很大的一個數對吧,那以二進制表示會是 0001 1001 1011 0001 0110 0000 1011 1100 1001 1111 0000,長度也才來到 44 bits,如果取一個長整數 (long) 的長度,也就是 64 bits,那最高的 20 bits 全是 0,產生出來的 UUID 會是五個 0 開頭,不要說這五個 0 什麼時候會變成 1,光是剛剛的時間戳記,最高的四個位元 0001 變成 0010是 2060 年的事了,變成 0011 是 2095 年,變成 0100 是 2130 年,等到 1111 用完需要進位,是 2513 年,到時候,我想 UUID 應該已經退出科技的舞台了。
小節
小節一下,ID 的產生其實是一門有趣的學問,會根據使用的情境選擇合適的 ID 產生方式,資料庫的 auto increment 不是不能用,實際上,它還是很好用的,只是產生的效率和使用的情境都會被資料庫綁住,這也是我越來越少用的原因。
回到文章的開頭,新模組選擇 UUID 還是 ULID 呢?都不是 (笑),最後,借鏡了 Slack 的模式,使用了單純的 {timestamp}.{seq}{random},seq 確保在同個程序中,相同時間取 ID 時遞增,再加上一個 random,讓衝突的機率降低,其實就算碰撞了,資料庫也會阻擋寫入,重新再產生一個 ID。
會整理這篇,主要是提醒自己,技術是會隨著時間不斷進化,定期檢視一些自己以為很瞭解的技術,是有必要的。
















