PhotobyMarkusSpiskeonUnsplash通用唯一標識符(UUID)是一個128位數字,用于標識計算機系統中的信息。它也被稱為全局唯一標">
> Photo by Markus Spiske on Unsplash
通用唯一標識符(UUID)是一個128位數字,用于標識計算機系統中的信息。 它也被稱為全局唯一標識符(GUID)。 UUID的標準表示形式通常由十六進制數字組成:
e5d520d8-d06e-4bc2-9bb9-a28e47014884
總共可以看到32個字符,中間有四個連字符。 為了不辜負他們的名字,UUID應該是唯一的。 實際上,通常情況并非如此-它們的唯一性取決于生成它們的方法或算法。 但是,雖然有可能,但是復制UUID的可能性足夠接近零,可以忽略不計。
有多種計算UUID的方法。 我將解釋差異是什么,并提供一些實現的見解。
第一個版本的UUID是使用計算機的MAC地址和生成時間生成的通用唯一標識符。
這是否意味著一個UUID版本是完全唯一的? 好吧,他們差不多。 確保唯一性的計算機(或節點)每秒限制為1630億,但這并不是開發人員經常遇到的問題。
此版本還有其他問題要擔心。 幾乎可以保證的唯一性是以匿名為代價的。 由于此版本需要考慮時間和唯一的MAC地址,因此可以確定計算機的地址和時間。
我將簡短地討論第二版。 實際上,第二版UUID與第一版相似,并且由于RFC 4122并未提供太多詳細信息而很少實現。 實際上,您根本不會使用它們。 它們也稱為DCE安全性UUID。
在第三版中,使用加密哈希和應用程序提供的文本字符串來生成UUID。 在此版本中,使用MD5哈希。 基本上,UUID是根據名稱生成的。 現在,我們可以使用名稱和名稱空間來創建一系列UUID。 MD5哈希算法是一種廣泛使用的哈希函數,可產生128位哈希值。
最初,MD5被設計為用作加密哈希函數,但現在似乎存在漏洞問題。 找到兩條散列為相同值的不同消息太容易了。 因此,它不是您要在應用程序中使用的版本。
偽隨機數生成器,也稱為確定性隨機位生成器,是一種用于生成數字序列的算法,該數字序列的屬性近似于隨機數序列的屬性。 —維基百科
此版本使用偽隨機數生成器生成UUID。 使用非常簡單。 字符串的每一位都是完全"隨機"生成的。 UUID仍然有可能被復制,但由于可能的組合數量很多,所以它很小。 確切的可能性數約為2 12?。
到今天為止,第四版已采用大多數編程語言實現。 它非常易于使用。 我將用Java舉例:
UUID uuid=UUID.randomUUID();
版本3和版本5之間的唯一區別是使用了不同的哈希算法。 第五版使用SHA-1而不是MD5。 從技術上講,它是一種出色的哈希算法,但它也可能遇到與第三版相同的問題。 我不建議使用它。
盡管幾乎每個版本都有其長處,但它們也有明顯的弱點,可能會損害您的應用程序設計。
第四版是完全隨機的,基本上是不可預測的,這是我建議現在使用的版本。
如果您不想在多個數據庫實例上發生沖突,或者您不想讓ID可預測或提供有關系統的信息,請考慮使用UUID。
UUID的最大好處是,它實際上是唯一的(與保證唯一性相反),對于日常使用來說仍然是完全可以接受的。
這是UUID的主要缺點:
· 例如,無需使用每次在數據庫中插入記錄時都會增加的索引,而是需要生成UUID。 這可能需要更長的時間。 它沒有說明創建的順序。 如果您需要訂購,請考慮不使用UUID對標識符以外的其他東西進行排序。
· 與他們一起工作可能有些奇怪。 UUID也沒有說任何有關其所屬實體的信息。 這會使調試變得更加困難,尤其是在測試中。
· 該值相當長,可能會影響性能。 但是,如今這些空間更便宜了。
但這也有一些重要的好處:
· UUID的主要優點是它是唯一的。 例如,用戶幾乎不可能將其猜測為URL參數(公開討論,因為這可能是安全問題)。 它們在應用程序中是唯一的。
· 它與環境無關。 您可以在任何地方(甚至離線)生成它,而無需依賴數據庫來生成標識符。 如果您有多個包含一個數據段(碎片)的數據庫,則UUID在所有數據庫中都是唯一的,而不僅僅是您現在所在的那個。 這使得在數據庫之間移動數據更加安全。
(本文翻譯自Dieter Jordens的文章《What Is a UUID and How Are They Generated?》,參考:https://medium.com/better-programming/what-is-a-uuid-and-how-are-they-generated-17f0acbd7233)
UID 的全稱是 Universally Unique Identifier,中文為通用唯一識別碼。
在對 UUID 進行說明之前,我們來看一個標準的 UUID。
下面就是一個標準的 UUID,使用橫杠分隔符來進行分隔:
123e4567-e89b-42d3-a456-556642440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
在結構中的 M,定義了 UUID 的版本,UUID 的 5 個版本就是在這里進行定義的。
這個版本是基于隨機數的,使用的基數為每 100 納秒為一個單位,時間的起點為1582年10月15日。同時還需要加上當前計算機的網卡物理地址(MAC)。
UUID-v2 和V1 很類似,是根據標識符(通常是組或用戶ID )、時間和節點ID 生成,不過區別在于V2 將V1 中的部分時間信息換成了主機名, 故應用具有局限性(有隱私風險),未大規模使用。
UUID-v3 通過散列(MD5 )名字空間(namespace )標識符和名稱生成。和V1 、V2 不同,V3 不依賴與機器信息和時間信息, 但是V3 要求輸入命名空間+名稱,命名空間本身也是一個UUID ,用來標識應用環境,名稱通常是用戶賬號、用戶名之類的內容,通過命名空間+名稱+三列算法算出UUID 。
UUID-v4 組成 UUID v4 的位是隨機生成的,沒有固有邏輯(除了第三段首個數字,該數字標識版本號),不包含命名空間、設備信息、時間信息。 故,UUID-v4 最容易理解、應用也最為廣泛。
UUID-v5 和V3 類似,區別在于散列算法,使用了sha1 散列算法。
可以認為 UUIDv5 就是為了強化 UUIDv3 出現的問題而使用新的哈希算法。
UUID(Universally Unique Identifier 通用唯一識別碼)用于標識資源唯一性。理論上說,門牌號、電話號碼、郵編、身份證號都是用來標識資源唯一性的,但為使用方便,不適合用一個無規律的字符串表示,UUID 主要還是在程序中使用。
UUID 源自1980年代的 Apollo 電腦公司,是一個 128 位的標識符,理論上的總數有 2128個,也就是說,哪怕每納秒產生 1 萬億個 UUID,也要 100 億年才能用完。因此,只要保證生成方法的散布足夠好,統計概率上,UUID 重復的可能性約等于 0 。
UUID 的具體規范可以參考 RFC 4122。這個標準定義了 5 個版本的 UUID:
我們常看到的 UUID 往往被表示為 16 進制數字和橫杠組成的字符串,比如:a3535b78-69dd-4a9e-9a79-57e2ea28981b,其中第二個橫杠之后的第一個數字表示 UUID 版本,例子中這個 UUID 就是版本 4 的。
大多數人在數據庫中存儲 UUID 的直接原因,是需要一個不暴露內部信息的唯一標識。例如博客文章,Title 無法保證不重復,數字 ID 則會暴露內部信息,于是,可以生成一個 UUID 作為唯一標識。類似地,我們在網上請求的許多公開資源,如圖片、音頻、以及其他文件等,都是以 UUID 作為標識的。
第二個原因,是為了方便數據管理:
一般不推薦把 UUID 作為主鍵,它會帶來不少問題:
我們知道,使用自增 ID 作為主鍵時,插入新的數據行往往是連續的,插入多條數據只需要讀寫少數數據頁。但由于 UUID 的隨機性,新插入的數據往往會落在不同的數據頁上,導致數據碎片化,同樣的數據量,可能需要更大的空間才能存儲。
同時,當數據量上升,內存中無法暫存足夠多的數據頁時,每次插入數據都可能涉及硬盤讀寫,極大地拖慢了數據插入效率。
大多數人會把 UUID 保存為 16 進制數字和橫杠組成的字符串,也就是 char(36),如果采用 UTF-8 字符集,它所占的字節數是 2 + 3 * 36=110 字節(前面 2 個字節為長度,后面每個字符 3 個字節)。相較而言,一個整數只有 4 個字節,相差 27 倍。
數據庫采用 B 樹索引,其中主鍵索引的葉子節點指向數據行,而二級索引的葉子節點存儲著主鍵,之后再通過主鍵索引回表查數據。也就是說,有多少個二級索引,主鍵就需要被存儲幾次,因此,索引的空間需求就極速擴張了。
在數據庫運行過程中,為加快查詢速度,這些索引往往需要被加載到內存中。那么,過大的索引導致內存不足,就會嚴重影響數據庫查詢效率。
CPU 每次最多可以比較 8 個字節的整數值,但對于字符串,必須一個字符一個字符比較過去。有測試說明,在查詢比較時,使用整數的速度比使用字符串的速度快數倍到數十倍之間。
不過,一般來說,數據庫不是一個 CPU 密集的應用,因此這方面的影響不是主要考慮因素。
將 UUID 存儲為 16 進制值和橫杠組成的字符串是非常低效的,UUID 本身只有 128 位,也就是 16 字節,存儲成字符串后卻有 110 個字節,膨脹了 7 倍,憑空多占了不少空間。優化的思路就是采用更好的格式,比如直接存儲二進制,或者將二進制值存儲為 base64 字符串。相對復雜一點的是將 UUID 映射到一個整數。
優化存儲格式的具體實現都不困難,也能相當程度地節約存儲空間,但并沒有解決 UUID 的隨機性帶來的數據碎片化的問題。
針對碎片化的問題,有一個思路是控制隨機性,也就是增加一個自己生成的字符串作為前綴,比如說日期(或它的哈希值)。因為字符串排序從前往后走,同樣的前綴也就意味著接近的排序,那么,原本散布在整個數據庫的值,就會集中分布在一定范圍內的數據頁,從而大大緩解了內存壓力。
更常見的思路,是使用自增 ID 作為主鍵,同時使用 UUID 作為唯一標識和與其它表關聯的外鍵。好處是有了一個可以比較安全地對外暴露的唯一標識,節約了索引空間,也不用擔心數據分片和數據重建帶來的危險。
但也存在一些問題,因為所有的外鍵關聯都用的 UUID,所以占用的空間自然會大一些,同時,字符串比較速度較慢和所有查詢都要回表也是值得考慮的因素。
很多小型應用不需要考慮數據管理的問題,只是需要一個可以對外暴露的唯一標識,于是,干脆放棄 UUID,采用其他思路實現標識的唯一性。
比如說前面說的博客文章,鑒于 Title 沒法保證唯一性,可以在 Title 前后加上一個前綴或者后綴,從而實現唯一性。一個思路是使用隨機字符串,或者作者、日期等信息。
又比如說,將每個數據行映射到一個大整數作為唯一標識。某種意義上等于根據自己的實際需要寫了一套新的唯一標識算法。
END