針對多類型資料庫,集群資料庫的有序GUID

来源:https://www.cnblogs.com/supersnowyao/archive/2018/01/23/8335397.html
-Advertisement-
Play Games

一、背景 常見的一種資料庫設計是使用連續的整數為做主鍵,當新的數據插入到資料庫時,由資料庫自動生成。但這種設計不一定適合所有場景。 隨著越來越多的使用Nhibernate、EntityFramework等ORM(對象關係映射)框架,應用程式被設計成為工作單元(Unit Of Work)模式,需要在數 ...


一、背景

     常見的一種資料庫設計是使用連續的整數為做主鍵,當新的數據插入到資料庫時,由資料庫自動生成。但這種設計不一定適合所有場景。

     隨著越來越多的使用Nhibernate、EntityFramework等ORM(對象關係映射)框架,應用程式被設計成為工作單元(Unit Of Work)模式,需要在數據持久化之前生成主鍵,為了保證在多線程併發以及站點集群環境中主鍵的唯一性,最簡單最常見的方式是將主鍵設計成為GUID類型。

     工作單元:是資料庫應用程式經常使用的一種設計模式,簡單一點來說,就是對多個資料庫操作進行打包,記錄對象上的所有變化,併在最後提交時一次性將所有變化通過系統事務寫入資料庫。目的是為了減少資料庫調用次數以及避免資料庫長事務。

     GUID(全球唯一標識符)也稱為UUID,是一種由演算法生成的二進位長度為128位的數字標識符。在理想情況下,任何電腦和電腦集群都不會生成兩個相同的GUID。GUID 的總數達到了2^128(3.4×10^38)個,所以隨機生成兩個相同GUID的可能性非常小,但並不為0。GUID一詞有時也專指微軟對UUID標準的實現。

     RFC 41222描述了創建標準GUID,如今大多數GUID生成演算法通常是一個很長的隨機數,再結合一些像網路MAC地址這種隨機的本地組件信息。

     GUID的優點允許開發人員隨時創建新值,而無需從資料庫伺服器檢查值的唯一性,這似乎是一個完美的解決方案。

     很多資料庫在創建主鍵時,為了充分發揮資料庫的性能,會自動在該列上創建聚集索引。

     我們先來說一說什麼是聚集索引。集索引確定表中數據的物理順序。聚集索引類似於電話簿,按姓氏排列數據。由於聚集索引規定數據在表中的物理存儲順序,因此一個表也只能包含一個聚集索引。它能夠快速查找到數據,但是如果插入資料庫的主鍵不在列表的末尾,向表中添加新行時就非常緩慢。例如,看下麵這個例子,在表中已經存在三行數據:

    

     此時非常簡單:數據行按對應ID列的順序儲存。如果我們新添加一行ID為8的數據,不會產生任何問題,新行會追加的末尾。

     

     但如果我們想插入一行的ID為5的數據:

      

     ID為7,8的數據行必須向下移動。雖然在這算什麼事兒,但當您的數據量達到數百萬行的級別之後,這就是個問題了。如果你還想要每秒處理上百次這種請求,那可真是難上加難了。

     這就是GUID主鍵引發的問題:它是隨機產生的,所以在數據插入時,隨時都會涉及到數據的移動,導致插入會很緩慢,還會涉及大量不必要的磁碟活動。總結果有兩點:

  • 空間的浪費以及由此帶來的讀寫效率的下降;
  • 更主要的,存儲的碎片化以及由此帶來的讀寫效率嚴重下降。

      GUID最關鍵的問題就是它是隨機的。我們需要設計一種有規則的GUID生成方式,在之後生成的GUID類型總是比之前的要大,保證插入資料庫的主鍵是在列表末尾追加的,這種我們稱之為有序GUID

 

二、GUID排序規則

     在講解有序GUID之前,我們必須先瞭解一下GUID在.Net中以及各個資料庫中的排序規則,排序規則不一樣,生成有序GUID的規則也會隨之變化。

     128位的GUID主要有4部分組成:Data1, Data2, Data3, and Data4,你可以看成下麵這樣:

11111111-2222-3333-4444-444444444444

     Data1占4個位元組、Data2占2個位元組、 Data3占2個位元組、Data4占8個位元組。我們分別的對個位元組編上序號:

序號1234 56 78 910 111213141516
Value 11 11 11 11 - 22 22 - 33 33 - 44 44 - 44 44 44 44 44 44

 

 

 

     GUID在.Net中的排序規則

     在.Net中,GUID預設的排序過段規則是按左到右的,看下麵這個示例。

 1 var list = new List<Guid> {
 2         new Guid("00000000-0000-0000-0000-010000000000"),
 3         new Guid("00000000-0000-0000-0000-000100000000"),
 4         new Guid("00000000-0000-0000-0000-000001000000"),
 5         new Guid("00000000-0000-0000-0000-000000010000"),
 6         new Guid("00000000-0000-0000-0000-000000000100"),
 7         new Guid("00000000-0000-0000-0000-000000000001"),
 8         new Guid("00000000-0000-0000-0100-000000000000"),
 9         new Guid("00000000-0000-0000-0010-000000000000"),
10         new Guid("00000000-0000-0001-0000-000000000000"),
11         new Guid("00000000-0000-0100-0000-000000000000"),
12         new Guid("00000000-0001-0000-0000-000000000000"),
13         new Guid("00000000-0100-0000-0000-000000000000"),
14         new Guid("00000001-0000-0000-0000-000000000000"),
15         new Guid("00000100-0000-0000-0000-000000000000"),
16         new Guid("00010000-0000-0000-0000-000000000000"),
17         new Guid("01000000-0000-0000-0000-000000000000")
18     };
19     list.Sort();
20 
21     foreach (Guid guid in list) {
22         Console.WriteLine(guid.ToString());
23     }

     輸出結果:

00000000-0000-0000-0000-000000000001
00000000-0000-0000-0000-000000000100
00000000-0000-0000-0000-000000010000
00000000-0000-0000-0000-000001000000
00000000-0000-0000-0000-000100000000
00000000-0000-0000-0000-010000000000
00000000-0000-0000-0010-000000000000
00000000-0000-0000-0100-000000000000
00000000-0000-0001-0000-000000000000
00000000-0000-0100-0000-000000000000
00000000-0001-0000-0000-000000000000
00000000-0100-0000-0000-000000000000
00000001-0000-0000-0000-000000000000
00000100-0000-0000-0000-000000000000
00010000-0000-0000-0000-000000000000
01000000-0000-0000-0000-000000000000

     通過上面的輸出結果,我們可以得到排序的權重如下:

序號1234 56 78 910 111213141516
權重 1 2 3 4   5 6   7 8   9 10   11 12 13 14 15 16
Value 11 11 11 11 - 22 22 - 33 33 - 44 44 - 44 44 44 44 44 44

 

 

 

      這與數字排序規則一致,從右到左進行依次進行排序(數字越小,權重越高,排序的優先順序越高)。

     GUID在各個資料庫中的排序規則

     在SQL Server資料庫中,我們有一種非常簡單的方式來比較兩個GUID類型的大小值(其實在SQL Server資料庫中稱為UniqueIdentifier類型):

 1 With UIDs As (--                         0 1 2 3  4 5  6 7  8 9  A B C D E F
 2             Select ID =  1, UID = cast ('00000000-0000-0000-0000-010000000000' as uniqueidentifier)
 3     Union   Select ID =  2, UID = cast ('00000000-0000-0000-0000-000100000000' as uniqueidentifier)
 4     Union   Select ID =  3, UID = cast ('00000000-0000-0000-0000-000001000000' as uniqueidentifier)
 5     Union   Select ID =  4, UID = cast ('00000000-0000-0000-0000-000000010000' as uniqueidentifier)
 6     Union   Select ID =  5, UID = cast ('00000000-0000-0000-0000-000000000100' as uniqueidentifier)
 7     Union   Select ID =  6, UID = cast ('00000000-0000-0000-0000-000000000001' as uniqueidentifier)
 8     Union   Select ID =  7, UID = cast ('00000000-0000-0000-0100-000000000000' as uniqueidentifier)
 9     Union   Select ID =  8, UID = cast ('00000000-0000-0000-0010-000000000000' as uniqueidentifier)
10     Union   Select ID =  9, UID = cast ('00000000-0000-0001-0000-000000000000' as uniqueidentifier)
11     Union   Select ID = 10, UID = cast ('00000000-0000-0100-0000-000000000000' as uniqueidentifier)
12     Union   Select ID = 11, UID = cast ('00000000-0001-0000-0000-000000000000' as uniqueidentifier)
13     Union   Select ID = 12, UID = cast ('00000000-0100-0000-0000-000000000000' as uniqueidentifier)
14     Union   Select ID = 13, UID = cast ('00000001-0000-0000-0000-000000000000' as uniqueidentifier)
15     Union   Select ID = 14, UID = cast ('00000100-0000-0000-0000-000000000000' as uniqueidentifier)
16     Union   Select ID = 15, UID = cast ('00010000-0000-0000-0000-000000000000' as uniqueidentifier)
17     Union   Select ID = 16, UID = cast ('01000000-0000-0000-0000-000000000000' as uniqueidentifier)
18 )
19 Select * From UIDs Order By UID, ID

     查詢結果:

IDUID
16 01000000-0000-0000-0000-000000000000
15 00010000-0000-0000-0000-000000000000
14 00000100-0000-0000-0000-000000000000
13 00000001-0000-0000-0000-000000000000
12 00000000-0100-0000-0000-000000000000
11 00000000-0001-0000-0000-000000000000
10 00000000-0000-0100-0000-000000000000
9 00000000-0000-0001-0000-000000000000
8 00000000-0000-0000-0010-000000000000
7 00000000-0000-0000-0100-000000000000
6 00000000-0000-0000-0000-000000000001
5 00000000-0000-0000-0000-000000000100
4 00000000-0000-0000-0000-000000010000
3 00000000-0000-0000-0000-000001000000
2 00000000-0000-0000-0000-000100000000
1 00000000-0000-0000-0000-010000000000

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

     通過上面可以得於是如下結果:

  1. 先按每1-8從左到右進行排序;
  2. 接著按第9-10位從右到左進行排序;
  3. 最後按後11-16位從右到左進行排序;

     通過分析,我們可得到如下權重列表:

序號1234 56 78 910 111213141516
權重 16 15 14 13   12 11   10 9   7 8   1 2 3 4 5 6
Value 11 11 11 11 - 22 22 - 33 33 - 44 44 - 44 44 44 44 44 44

 

 

 

     在Microsoft官方文檔中,有一篇文檔關於GUID與uniqueidentifier的值比較:Comparing GUID and uniqueidentifier Values

     不同的資料庫處理GUID的方式也是不同的:

          1)在SQL Server存在內置GUID類型,沒有原生GUID支持的資料庫通過模擬方式來實現的;

          2)在Oracle保存為raw bytes類型,具體類型為raw(16)

          3)在MySql中通常將GUID儲存為char(36)的字元串形式;

     關於Oracle、MySql資料庫的排序規則與.Net中排序規則,不過篇章的限制,這裡不再做具體的演示。在github上提供了示例SQL語句:https://gist.github.com/tangdf/f0aed064ba10bfa0050e4344b9236889。我們在這裡只給出最終的結論:

小結:

  1. .Net中GUID的排序規則是從左到右依次進行排序,與數字排序規則一致;
  2. Sql Server資料庫提供對GUID類型的支持,在資料庫中稱為UniqueIdentifier類型,但是排序規則比較複雜:Oracle資料庫未提供對GUID類型的支持,使用的是raw bytes類型保存數據raw(16),具體類型為,排序規則與GUID在.Net中規則一致;
    • 先按每1-8從左到右進行排序;
    • 接著按第9-10位從右到左進行排序;
    • 最後按後11-16位從右到左進行排序;
  3. Oracle資料庫未提供對GUID類型的支持,使用的是raw bytes類型保存數據raw(16),具體類型為,排序規則與GUID在.Net中規則一致;
  4. MySql數據未提供對GUID類型的支持,使用的是字元串的類型保存數據,使用是的char(36)類型,由於使用的是字元串類型,排序規則與GUID在.Net中的規則一致。

 

三、有序GUID

     有序GUID是有規則的生成GUID,保存在之後生成的GUID類型總是比之前的要大。不過在上一節中,已經提到過各個資料庫對GUID支持不一樣,而且排序的規則也不一樣,所以我們需要為每一個資料庫提供不一致的有序GUID生成規則。

     UuidCreateSequential函數

     我們都知道SQL Server資料庫有一個NewSequentialId()函數,用於創建有序GUID。在創建表時,可以將它設置成為GUID類型欄位的預設值,在插入新增數據時自動創建主鍵的值(該函數只能做為欄位的預設值,不能直接在SQL中調用)。

     示例:

1 Create Table TestTable
2        (
3          ID UniqueIdentifier Not Null Default ( NewSequentialId() ) ,
4          Number Int
5        );

     NewSequentialId()函數只能在資料庫使用,不過在 Microsoft 的 MSDN 文檔中有說明,NEWSEQUENTIALID 是對 Windows UuidCreateSequential 函數的包裝https://msdn.microsoft.com/zh-cn/library/ms189786(v=sql.120).aspx。這樣我們可以在C#通過非托管方法調用:

 1 [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)]
 2    private static extern int UuidCreateSequential(out Guid guid);
 3 
 4    public static Guid NewSequentialGuid()
 5    {
 6        const int RPC_S_OK = 0;
 7 
 8        int result = UuidCreateSequential(out var guid);
 9        if (result != RPC_S_OK) {
10            throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error());
11        }
12 
13        return guid;
14    }

     不這個方法也存在三個問題:

  1. 這個方法涉及到安全問題,UuidCreateSequential函數依賴的計算硬體,該方法的後12位其實是網卡的MAC地址。這是我電腦生成的一組有序GUID。

{A2A93393-C8DC-11E7-B133-2C56DC497A97}
{A2A93394-C8DC-11E7-B133-2C56DC497A97}
{A2A93395-C8DC-11E7-B133-2C56DC497A97}
{A2A93396-C8DC-11E7-B133-2C56DC497A97}
{A2A93397-C8DC-11E7-B133-2C56DC497A97}
{A2A93398-C8DC-11E7-B133-2C56DC497A97}
{A2A93399-C8DC-11E7-B133-2C56DC497A97}
{A2A9339A-C8DC-11E7-B133-2C56DC497A97}
{A2A9339B-C8DC-11E7-B133-2C56DC497A97}
{A2A9339C-C8DC-11E7-B133-2C56DC497A97}

     這是我電腦的網卡的MAC地址:

    

  1. 由於UuidCreateSequential函數生成的有序GUID中包括MAC地址,所以如果在伺服器集群環境中,肯定存在一臺伺服器A上生成的有序GUID總比另一臺伺服器B生成要更小,伺服器A產生的數據插入到資料庫時,由於聚集索引的問題,總是會移動伺服器B已經持久化到資料庫中的數據。集群的伺服器越多,產生的IO問題更嚴重。在伺服器群集環境中,需要自行實現有序GUID。

  2. UuidCreateSequential函數生成的GUID規則與SQL Server中排序的規則存在不一致,這樣仍然會導致嚴重的IO問題,所以需要將GUID重新排序後再持久化到資料庫。例如上面列出生成的GUID列表,依次生成的數據可以看出,是第4位位元組在自增長,在這與任何一個資料庫的排序規則都不一致;關於該函數生成的規則,可以見此鏈接:https://stackoverflow.com/questions/5585307/sequential-guids

     下麵的方法是將生成的GUID調整成為適合Sql Server使用的有序GUID(針對其它資料庫支持,您可以按排序規則自行修改):

 1 [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)]
 2 static extern int UuidCreateSequential(byte[] buffer);
 3 
 4 static Guid NewSequentialGuid() {
 5 
 6     byte[] raw = new byte[16];
 7     if (UuidCreateSequential(raw) != 0)
 8         throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error());
 9 
10     byte[] fix = new byte[16];
11 
12     // reverse 0..3
13     fix[0x0] = raw[0x3];
14     fix[0x1] = raw[0x2];
15     fix[0x2] = raw[0x1];
16     fix[0x3] = raw[0x0];
17 
18     // reverse 4 & 5
19     fix[0x4] = raw[0x5];
20     fix[0x5] = raw[0x4];
21 
22     // reverse 6 & 7


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近項目中需要頻繁用到伺服器返回的Cookie,由於項目採用的是HttpClient,並且用CookieContainer自動托管Cookie,在獲取Cookie的時候不太方便。所以就寫了個拓展類。 首先的遍歷獲取所有的Cookie,然後存入字典當中,再把字典存入記憶體緩存中,在下次調用方法的時候,先 ...
  • 在應用程式的開發過程中,我們往往會為軟體提供一些配置項,以允許軟體根據配置項靈活來做事情,比如配置日誌文件路徑等,此外,我們還可以用配置項來為用戶存儲其偏好設置等。 .NET 為我們預設提供了配置機制以及配置文件,項目中的 app.config 或者 web.config 文件(如果沒有,可以添加) ...
  • System.Net.WebSockets.WebSocket 需要.NET 4.5,IIS8以上,Windows Server2008R2自帶的IIS不支持,Windows8及Server2012以上自帶的IIS可以 1、實現IHttpHandler用於處理webSocket請求 2、在web.C ...
  • 在ASP.NET的web.config中,可以用兩種方式來寫連接字元串的配置。 如上代碼所示:兩種方式為appSettings和connectionStrings appSettings: ①它是asp.net1.1的時候用的,在vs2003中用的 ②裡面存的相當於鍵值對的形式,key和value。 ...
  • 首先,感謝surging的作者fanliang11為.net開源做出的貢獻 其次, surging 的git地址:https://github.com/dotnetcore/surging surging 的整體設計思想:http://www.cnblogs.com/fanliang11/p/719 ...
  • 上一篇講瞭如何再控制台將RocketMQ跑起來,本篇講解,在asp.net mvc種跑起來,含(發佈、訂閱)。 本次將不挨個貼源碼,直接展示目錄,根據上一篇文章,進行相應的調整即可。 1.新建一個類庫,將MQ公共部分提出來: 如: 2.新建一個asp.net mvc 項目(需要在App_Data中修 ...
  • 代碼如下: static class Ulity { public static string[] Split(this string source,int count) { List<string> list = new List<string>(); string temp = string.E ...
  • 1.將線程設置為應用程式空閑處理 Dispatcher.Invoke(new Action(() => { 載入控制項; }), System.Windows.Threading.DispatcherPriority.ApplicationIdle); 2.提示等待框 在新線程中啟動等待框 http: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...