適合用於資料庫主鍵的最佳UUID工具庫 - Vlad Mihalcea
在本文中,我們將瞭解哪種 UUID(通用唯一識別符號)型別最適合具有主鍵約束的資料庫列。
雖然標準的 128 位隨機 UUID 是一個非常受歡迎的選擇,但您會發現這非常適合資料庫主鍵列。
通用唯一識別符號 (UUID) 是一個 128 位偽隨機序列,可以獨立生成,無需單個集中式系統負責確保識別符號的唯一性。
RFC 4122 規範定義了UUID 的五個標準化版本,它們由各種資料庫函式或程式語言實現。
例如,UUID()MySQL 函式返回版本 1 UUID 編號。
並且 JavaUUID.randomUUID()函式返回版本 4 UUID 編號。
對於許多開發人員來說,使用這些標準 UUID 作為資料庫識別符號非常有吸引力,因為:
- ids 可以由應用程式生成。因此不需要中央協調。
- 識別符號衝突的可能性極低。
- id 值是隨機的,您可以安全地將它傳送到 UI,因為使用者將無法猜測其他識別符號值並使用它們來檢視其他人的資料。
但是,出於多種原因,使用隨機 UUID 作為資料庫表主鍵不是一個好主意。
首先,UUID 很大。每條記錄都需要 16 個位元組作為資料庫識別符號,這也會影響所有關聯的外來鍵列。
其次,Primary Key 列通常有一個關聯的 B+Tree 索引來加速查詢或連線,B+Tree 索引按排序順序儲存資料。
然而,使用 B+Tree 索引隨機值會導致很多問題:
- 索引頁面將具有非常低的填充因子,因為這些值是隨機出現的。因此,一個 8kB 的頁面最終將只儲存幾個元素,因此在磁碟和資料庫記憶體中浪費了大量空間,因為索引頁面可以快取在緩衝池中。
- 由於 B+Tree 索引需要重新平衡自身以保持其等距樹結構,隨機鍵值將導致更多的索引頁拆分和合並,因為沒有預先確定的填充樹結構的順序。
如果你使用的是 SQL Server 或 MySQL,那就更糟了,因為整個表基本上是一個聚集索引。
事實上,幾乎所有資料庫專家都會告訴您避免使用標準 UUID 作為資料庫表主鍵:
TSID – 按時間排序的唯一識別符號
如果您計劃將 UUID 值儲存在主鍵列中,那麼您最好使用 TSID(按時間排序的唯一識別符號)。
TSID Creator OSS 庫提供了一種此類實現,它提供了一個由兩部分組成的 64 位 TSID:
- 一個 42 位時間元件
- 一個 22 位隨機分量
隨機成分有兩部分:
- 節點識別符號(0 到 20 位)
- 一個計數器(2 到 22 位)
tsidcreator.node引導應用程式時,系統屬性可以提供節點識別符號:
-Dtsidcreator.node="12"
節點識別符號也可以透過環境變數提供TSIDCREATOR_NODE:
export TSIDCREATOR_NODE="12"
該庫在 Maven Central 上可用,因此您可以透過以下依賴項獲取它:
<dependency> <groupId>com.github.f4b6a3</groupId> <artifactId>tsid-creator</artifactId> <version>${tsid-creator.version}</version> </dependency> |
您可以建立一個Tsid最多可以使用 256 個節點的物件,如下所示:
Tsid tsid = TsidCreator.getTsid256();
從Tsid物件中,我們可以提取以下值:
64 位數值,
編碼 64 位值的Crockford 的 Base32 字串值,
儲存在42-bit 序列中的紀元以來的 Unix 毫秒數
為了視覺化這些值,我們可以將它們列印到日誌中:
long tsidLong = tsid.toLong(); String tsidString = tsid.toString(); long tsidMillis = tsid.getUnixMilliseconds(); LOGGER.info( "TSID numerical value: {}", tsidLong ); LOGGER.info( "TSID string value: {}", tsidString ); LOGGER.info( "TSID time millis since epoch value: {}", tsidMillis ); |
我們得到以下輸出:
TSID numerical value: 388400145978465528
TSID string value: 0ARYZVZXW377R
TSID time millis since epoch value: 1670438610927
生成十個值時:
for (int i = 0; i < 10; i++) { LOGGER.info( "TSID numerical value: {}", TsidCreator.getTsid256().toLong() ); } |
我們可以看到值是單調遞增的:
TSID numerical value: 388401207189971936
TSID numerical value: 388401207189971937
TSID numerical value: 388401207194165637
TSID numerical value: 388401207194165638
TSID numerical value: 388401207194165639
TSID numerical value: 388401207194165640
TSID numerical value: 388401207194165641
TSID numerical value: 388401207194165642
TSID numerical value: 388401207194165643
TSID numerical value: 388401207194165644
避免同步
因為透過TsidCreator工具提供的預設TSID工廠帶有一個同步的隨機值生成器,所以最好使用一個自定義的TsidFactory,提供以下最佳化。
- 它可以使用ThreadLocalRandom生成隨機值,因此避免了同步塊上的執行緒阻塞
- 它可以使用少量的節點位,因此為隨機生成的數值留下更多的位。
因此,我們可以定義下面的TsidUtil,它為我們提供了一個TsidFactory,在我們想要生成一個新的Tsid物件時使用。
public static class TsidUtil { public static final String TSID_NODE_COUNT_PROPERTY = "tsid.node.count"; public static final String TSID_NODE_COUNT_ENV = "TSID_NODE_COUNT"; public static TsidFactory TSID_FACTORY; static { String nodeCountSetting = System.getProperty( TSID_NODE_COUNT_PROPERTY ); if(nodeCountSetting == null) { nodeCountSetting = System.getenv( TSID_NODE_COUNT_ENV ); } int nodeCount = nodeCountSetting != null ? Integer.parseInt(nodeCountSetting) : 256; int nodeBits = (int) (Math.log(nodeCount) / Math.log(2)); TSID_FACTORY = TsidFactory.builder() .withRandomFunction(length -> { final byte[] bytes = new byte[length]; ThreadLocalRandom.current().nextBytes(bytes); return bytes; }) .withNodeBits(nodeBits) .build(); } } |
結論
使用標準 UUID 作為主鍵值不是一個好主意,除非第一個位元組是單調遞增的。
因此,使用按時間排序的 TSID 是一個更好的主意。它不僅需要標準 UUID 一半的位元組數,而且更適合作為 B+Tree 索引鍵。
雖然 SQL Server 透過 提供按時間排序的 GUID NEWSEQUENTIALID,但 GUID 的大小為 128 位,因此它是 TSID 的兩倍。
UUID 規範的第 7 版也存在同樣的問題,它提供了按時間排序的 UUID。但是,它使用相同的規範格式(128 位),但格式太大了。每個引用外來鍵列都會放大主鍵列儲存的影響。
如果您所有的主鍵都是 128 位 UUID,那麼主鍵和外來鍵索引將需要大量空間,包括磁碟和資料庫記憶體,因為緩衝池同時包含表和索引頁。
相關文章
- 什麼是單主資料庫複製? -Vlad Mihalcea資料庫
- 批處理最佳實踐 - Vlad Mihalcea
- 使用 Spring Transactional 註釋的最佳方式 - Vlad MihalceaSpring
- indexedDB 資料庫主鍵Index資料庫
- 使用Spring實現訪問主從資料庫的讀寫和只讀事務/事物的分離路由 -Vlad MihalceaSpring資料庫路由
- SQL 搜尋方法或鍵集分頁 - Vlad MihalceaSQL
- 資料庫主鍵、從鍵(易懂版)資料庫
- 顯示資料庫中表的主鍵資料庫
- 關於資料庫表記錄主鍵生成的問題?資料庫
- 資料庫主鍵 ID 生成策略資料庫
- 使用JPA和Hibernate呼叫儲存過程的最佳方法 - Vlad Mihalcea儲存過程
- 使用FlexyPool度量你的XA事務連線池合適大小 - Vlad MihalceaFlex
- Java MyBatis 插入資料庫返回主鍵JavaMyBatis資料庫
- 資料庫主鍵是自增好還是UUID好,分散式環境下如何保證主鍵的唯一性資料庫UI分散式
- 資料庫模型設計——主鍵的設計資料庫模型
- 不要使用業務鍵作為資料庫主鍵資料庫
- 資料庫主鍵設計之思考(ZT)資料庫
- 資料庫系列:主從延時最佳化資料庫
- 使用JPA和Hibernate延遲載入實體屬性的最佳方法 - Vlad Mihalcea
- 資料表設計之主鍵自增、UUID或聯合主鍵UI
- mybatis oracle資料庫批次插入資料,忽略主鍵重複MyBatisOracle資料庫
- 使用Hashids來保護你的資料庫主鍵資料庫
- 資料庫自增主鍵可能產生的問題資料庫
- MySQL 資料庫自增主鍵生成的優缺點MySql資料庫
- 根據開源資料庫選擇合適的工具資料庫
- Laravel指東:使用模型建立 uuid 主鍵資料的兩種方式Laravel模型UI
- 菜鳥學資料庫(四)——超鍵、候選鍵、主鍵、外來鍵資料庫
- 資料庫中主庫和從庫的關係資料庫
- 應用適配資料庫還是資料庫適配應用資料庫
- 活字格效能最佳化技巧(1)——如何利用資料庫主鍵提升訪問效能資料庫
- NoSQL 資料庫的主主備份SQL資料庫
- 獲取不同資料庫新增記錄主鍵值資料庫
- [資料庫][分庫分表]分庫分表之後,id主鍵如何處理資料庫
- 將Standby資料庫臨時轉換為主資料庫用於測試資料庫
- mysql主庫清理資料,從庫保留MySql
- 當資料庫表無主鍵ID時,ORM這樣更新資料資料庫ORM
- 主用ATC資料庫資料庫
- 對於最近2天的bi資料庫的最佳化資料庫