鏈接:https://zhuanlan.zhihu.com/p/62494795
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
什么是UUID?
UUID全稱:Universally Unique Identifier,即通用唯一識別碼。
UUID是由一組32位數的16進制數字所構成,是故UUID理論上的總數為16^32 = 2^128,約等於3.4 x 10^38。也就是說若每納秒產生1兆個UUID,要花100億年才會將所有UUID用完。
UUID的標准型式包含32個16進制數字,以連字號分為五段,形式為8-4-4-4-12的32個字符,如:550e8400-e29b-41d4-a716-446655440000。
UUID的作用
UUID的是讓分布式系統中的所有元素都能有唯一的辨識信息,而不需要通過中央控制端來做辨識信息的指定。如此一來,每個人都可以創建不與其它人沖突的UUID。在這樣的情況下,就不需考慮數據庫創建時的名稱重復問題。目前最廣泛應用的UUID,是微軟公司的全局唯一標識符(GUID),而其他重要的應用,則有Linux ext2/ext3文件系統、LUKS加密分區、GNOME、KDE、Mac OS X等等。
UUID的組成
UUID是指在一台機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的。通常平台會提供生成的API。按照開放軟件基金會(OSF)制定的標准計算,用到了以太網卡地址、納秒級時間、芯片ID碼和許多可能的數字。
UUID由以下幾部分的組合:
- 當前日期和時間,UUID的第一個部分與時間有關,如果你在生成一個UUID之后,過幾秒又生成一個UUID,則第一個部分不同,其余相同。
- 時鍾序列。
- 全局唯一的IEEE機器識別號,如果有網卡,從網卡MAC地址獲得,沒有網卡以其他方式獲得。
UUID的唯一缺陷在於生成的結果串會比較長。關於UUID這個標准使用最普遍的是微軟的GUID(Globals Unique Identifiers)。
UUID的生成
public static void main(String[] args) throws Exception {
System.out.println(UUID.randomUUID());
}
批量生成UUID網站: http://www.uuid.online/
更多Java好文請關注Java技術棧微信公眾號,在公眾號后台回復關鍵字:java,以下僅為部分預覽。
- 出場率比較高的一道多線程安全面試題
- Java類初始化順序,大神3個示例帶你躺坑
- switch case 支持的 6 種數據類型!
- 面試常考:Synchronized 有幾種用法?
- Hashtable 為什么不叫 HashTable?
============================拓展閱讀==================================
鏈接:https://zhuanlan.zhihu.com/p/259802265
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
1. UUID 是什么?
UUID(Universally Unique Identifier 通用唯一識別碼)用於標識資源唯一性。理論上說,門牌號、電話號碼、郵編、身份證號都是用來標識資源唯一性的,但為使用方便,不適合用一個無規律的字符串表示,UUID 主要還是在程序中使用。
UUID 源自1980年代的 Apollo 電腦公司,是一個 128 位的標識符,理論上的總數有 2128個,也就是說,哪怕每納秒產生 1 萬億個 UUID,也要 100 億年才能用完。因此,只要保證生成方法的散布足夠好,統計概率上,UUID 重復的可能性約等於 0 。
UUID 的具體規范可以參考 RFC 4122。這個標准定義了 5 個版本的 UUID:
- 版本 1 根據時間和 MAC 地址來生成 UUID。MAC 地址用於保證設備唯一性。通過在時間戳后加入 13-14 位的時鍾序列,可以保證在同一台設備,同 1 秒內生成的 1630 億個 UUID 不重復。
這個版本的 UUID 我們用得相對較少,一個原因是其中攜帶了設備信息。1999 年,著名病毒梅麗莎的作者,因為代碼中的 UUID 暴露了 MAC 地址信息,不到一個星期就被抓住了。另一個原因是這個版本的 UUID 生成有規律,比較容易根據一個 UUID 推斷到下一個 UUID。 - 版本 2 是一個 DEC 安全的版本,RFC4122 中也沒有詳細說明具體實現方式,我們一般也用不到。
- 版本 3 根據字符串和命名空間散列值(HASH)來獲取 UUID。由於 HASH 函數本身的特性,一般不用擔心 UUID 沖突,或者別人根據散列值反推原數據的問題。這個版本采用的是 MD5 散列值。
- 版本 4 根據隨機數生成 UUID,是我們比較常用的版本。
- 版本 5 和版本 3 一樣,也是根據散列值獲取 UUID,不過采用的散列算法是 SHA-1 而不是 MD5,相較而言,RFC4122 推薦大家使用版本 5 而不是版本 3。
我們常看到的 UUID 往往被表示為 16 進制數字和橫杠組成的字符串,比如:a3535b78-69dd-4a9e-9a79-57e2ea28981b
,其中第二個橫杠之后的第一個數字表示 UUID 版本,例子中這個 UUID 就是版本 4 的。
2. 為什么在數據庫中使用 UUID?
大多數人在數據庫中存儲 UUID 的直接原因,是需要一個不暴露內部信息的唯一標識。例如博客文章,Title
無法保證不重復,數字 ID
則會暴露內部信息,於是,可以生成一個 UUID 作為唯一標識。類似地,我們在網上請求的許多公開資源,如圖片、音頻、以及其他文件等,都是以 UUID 作為標識的。
第二個原因,是為了方便數據管理:
- 當數據量過大,不得不進行分片管理的時候,數字 ID 的唯一性不好保證;萬一需要重建部分數據,數字 ID 也很難確保與原表一致。
- UUID 作為預先生成的值,可以在插入數據庫之前拿到,會方便一些數據操作。
3. UUID 作為主鍵有什么問題?
一般不推薦把 UUID 作為主鍵,它會帶來不少問題:
3.1 數據碎片化
我們知道,使用自增 ID 作為主鍵時,插入新的數據行往往是連續的,插入多條數據只需要讀寫少數數據頁。但由於 UUID 的隨機性,新插入的數據往往會落在不同的數據頁上,導致數據碎片化,同樣的數據量,可能需要更大的空間才能存儲。
同時,當數據量上升,內存中無法暫存足夠多的數據頁時,每次插入數據都可能涉及硬盤讀寫,極大地拖慢了數據插入效率。
3.2 索引占用空間過大
大多數人會把 UUID 保存為 16 進制數字和橫杠組成的字符串,也就是 char(36),如果采用 UTF-8 字符集,它所占的字節數是 2 + 3 * 36 = 110 字節(前面 2 個字節為長度,后面每個字符 3 個字節)。相較而言,一個整數只有 4 個字節,相差 27 倍。
數據庫采用 B 樹索引,其中主鍵索引的葉子節點指向數據行,而二級索引的葉子節點存儲着主鍵,之后再通過主鍵索引回表查數據。也就是說,有多少個二級索引,主鍵就需要被存儲幾次,因此,索引的空間需求就極速擴張了。
在數據庫運行過程中,為加快查詢速度,這些索引往往需要被加載到內存中。那么,過大的索引導致內存不足,就會嚴重影響數據庫查詢效率。
3.3 字符比較比整數比較慢
CPU 每次最多可以比較 8 個字節的整數值,但對於字符串,必須一個字符一個字符比較過去。有測試說明,在查詢比較時,使用整數的速度比使用字符串的速度快數倍到數十倍之間。
不過,一般來說,數據庫不是一個 CPU 密集的應用,因此這方面的影響不是主要考慮因素。
4. 替代方案討論
4.1 優化 UUID 的存儲
將 UUID 存儲為 16 進制值和橫杠組成的字符串是非常低效的,UUID 本身只有 128 位,也就是 16 字節,存儲成字符串后卻有 110 個字節,膨脹了 7 倍,憑空多占了不少空間。優化的思路就是采用更好的格式,比如直接存儲二進制,或者將二進制值存儲為 base64 字符串。相對復雜一點的是將 UUID 映射到一個整數。
優化存儲格式的具體實現都不困難,也能相當程度地節約存儲空間,但並沒有解決 UUID 的隨機性帶來的數據碎片化的問題。
針對碎片化的問題,有一個思路是控制隨機性,也就是增加一個自己生成的字符串作為前綴,比如說日期(或它的哈希值)。因為字符串排序從前往后走,同樣的前綴也就意味着接近的排序,那么,原本散布在整個數據庫的值,就會集中分布在一定范圍內的數據頁,從而大大緩解了內存壓力。
4.2 同時使用自增 ID 和 UUID
更常見的思路,是使用自增 ID 作為主鍵,同時使用 UUID 作為唯一標識和與其它表關聯的外鍵。好處是有了一個可以比較安全地對外暴露的唯一標識,節約了索引空間,也不用擔心數據分片和數據重建帶來的危險。
但也存在一些問題,因為所有的外鍵關聯都用的 UUID,所以占用的空間自然會大一些,同時,字符串比較速度較慢和所有查詢都要回表也是值得考慮的因素。
4.3 避開 UUID
很多小型應用不需要考慮數據管理的問題,只是需要一個可以對外暴露的唯一標識,於是,干脆放棄 UUID,采用其他思路實現標識的唯一性。
比如說前面說的博客文章,鑒於 Title 沒法保證唯一性,可以在 Title 前后加上一個前綴或者后綴,從而實現唯一性。一個思路是使用隨機字符串,或者作者、日期等信息。
又比如說,將每個數據行映射到一個大整數作為唯一標識。某種意義上等於根據自己的實際需要寫了一套新的唯一標識算法。