SQL Server 有序GUID,SequentialGuid,


問題描述


有序的GUID性能對比,堪比自增ID integer

 

一個大神告訴我NEWSEQUENTIALID() 在數據遷移的時候會有問題(感謝大神指點),所以我就深挖一下這個函數。

    關於NEWSEQUENTIALID() 的用法 參照  NEWSEQUENTIALID()

NEWSEQUENTIALID 是對 Windows UuidCreateSequential 函數的包裝。

https://msdn.microsoft.com/zh-cn/library/ms189786(v=sql.120).aspx

我們系統中對UuidCreateSequential 方法的擴展是這樣寫的,代碼如下:

 

    public static class GuidExtension
    {
        [DllImport("rpcrt4.dll", SetLastError = true)]
        public static extern int UuidCreateSequential(out Guid guid);
        private const int RPC_S_OK = 0;

        public static Guid CreateRpcrt4Guid()
        {
            Guid guid;
            int result = UuidCreateSequential(out guid);
            if (result == RPC_S_OK)
            {
                byte[] guidBytes = guid.ToByteArray();
                Array.Reverse(guidBytes, 0, 4);
                Array.Reverse(guidBytes, 4, 2);
                Array.Reverse(guidBytes, 6, 2);

                return new Guid(guidBytes);
            }
            else
                return Guid.NewGuid();
        }

    }

 

  有以下幾個缺點:

  1、暴漏MAC地址:NEWSEQUENTIALID函數最后6個字符是網卡的MAC地址

  可以執行看一下

create table #t
(
    id uniqueidentifier not null  default newsequentialid()
    ,name varchar(100)
)
go

insert into #t(name)
output inserted.id
values('a')
  

 

  2、如果進行數據遷移,到另一台機器上,MAC地址改變就會引起頁的爭用。

    因為GUID在的SQL Server的值大小的比對是這樣的:

with uids as (
            select id =  1, uuid = cast ('00000000-0000-0000-0000-010000000000' as uniqueidentifier)
    union   select id =  2, uuid = cast ('00000000-0000-0000-0000-000100000000' as uniqueidentifier)
    union   select id =  3, uuid = cast ('00000000-0000-0000-0000-000001000000' as uniqueidentifier)
    union   select id =  4, uuid = cast ('00000000-0000-0000-0000-000000010000' as uniqueidentifier)
    union   select id =  5, uuid = cast ('00000000-0000-0000-0000-000000000100' as uniqueidentifier)
    union   select id =  6, uuid = cast ('00000000-0000-0000-0000-000000000001' as uniqueidentifier)
    union   select id =  7, uuid = cast ('00000000-0000-0000-0100-000000000000' as uniqueidentifier)
    union   select id =  8, uuid = cast ('00000000-0000-0000-0010-000000000000' as uniqueidentifier)
    union   select id =  9, uuid = cast ('00000000-0000-0001-0000-000000000000' as uniqueidentifier)
    union   select id = 10, uuid = cast ('00000000-0000-0100-0000-000000000000' as uniqueidentifier)
    union   select id = 11, uuid = cast ('00000000-0001-0000-0000-000000000000' as uniqueidentifier)
    union   select id = 12, uuid = cast ('00000000-0100-0000-0000-000000000000' as uniqueidentifier)
    union   select id = 13, uuid = cast ('00000001-0000-0000-0000-000000000000' as uniqueidentifier)
    union   select id = 14, uuid = cast ('00000100-0000-0000-0000-000000000000' as uniqueidentifier)
    union   select id = 15, uuid = cast ('00010000-0000-0000-0000-000000000000' as uniqueidentifier)
    union   select id = 16, uuid = cast ('01000000-0000-0000-0000-000000000000' as uniqueidentifier)
)
select * from uids order by uuid desc

 

輸出結果:

  類似 漢字的三點水偏旁(為了好記)


從這里可以看出,MAC地址對GUID的大小有這最高的決定性,這就導致在數據遷移的時候出問題。

 

COMB解決方案


 

 COMB 類型的GUID 基本設計思路是這樣的:既然GUID數據生成是隨機的造成索引效率低下,影響了系統的性能,那么能不能通過組合的方式,保留GUID的前10個字節,用后6個字節表示GUID生成的時間(DateTime),這樣我們將時間信息與GUID組合起來,在保留GUID的唯一性的同時增加了有序性,以此來提高索引效率。

 

前十個字節是通過隨機數生成

private static readonly RNGCryptoServiceProvider RandomGenerator = new RNGCryptoServiceProvider();

      byte[] randomBytes = new byte[10];
      RandomGenerator.GetBytes(randomBytes);

 

 

后六個字節用時間生成

      long timestamp = DateTime.UtcNow.Ticks / 10000L;
      byte[] timestampBytes = BitConverter.GetBytes(timestamp);

      if (BitConverter.IsLittleEndian)
      {
        Array.Reverse(timestampBytes);
      }

 

 

最后組合起來

    byte[] guidBytes = new byte[16];
    Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 10);
    Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6); 

    return new Guid(guidBytes);

 

 

這個解決方法是被大家所認可的,唯一感覺不好的地方是,在快速獲取很多的GUID的時候,時間是一樣的,加上隨機生成的數據,這一組數據是大小不一的。假如數據庫里有很多數據,這一組數據肯定比他們大,性能應該沒有問題。

github地址:

https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs#L25-L72

https://github.com/jhtodd/SequentialGuid/

 

我的解決方法


 

總結上面的方法,UuidCreateSequential 前面10個字節有序,后6個是MAC地址。COMBO解決方案是前面10個隨機,后六個是時間。我是將這兩個結合起來

前10個去UuidCreateSequential 方法的值,后6個取時間

代碼:

public static Guid NewSequentialGuid()
    {
        const int RPC_S_OK = 0;
        Guid guid;
        int result = UuidCreateSequential(out  guid);

        if (result != RPC_S_OK)
        {
            throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error());
        }
        else
        {
       //這里把UuidCreateSequential函數返回的數據做處理
byte[] guidBytes = guid.ToByteArray(); Array.Reverse(guidBytes, 0, 4); Array.Reverse(guidBytes, 4, 2); Array.Reverse(guidBytes, 6, 2);
       //這里用時間
long timestamp = DateTime.UtcNow.Ticks / 10000L; byte[] timestampBytes = BitConverter.GetBytes(timestamp); if (BitConverter.IsLittleEndian) { Array.Reverse(timestampBytes); }
       //最后把時間賦值給后6位 Buffer.BlockCopy(timestampBytes,
2, guidBytes, 10, 6); return new Guid(guidBytes); } } [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)] private static extern int UuidCreateSequential(out Guid guid);

 

這里可以在程序調用,作為DBA在數據庫使用的話可以將這個方法添加到程序集里,需要有些改動

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlTypes;

public class FunctionNewGuid
{
  //這里需要添加SqlFunction屬性
  //返回類型是數據庫類型
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlGuid NewSequentialGuid() { const int RPC_S_OK = 0; Guid guid; int result = UuidCreateSequential(out guid); if (result != RPC_S_OK) { throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error()); } else { byte[] guidBytes = guid.ToByteArray(); Array.Reverse(guidBytes, 0, 4); Array.Reverse(guidBytes, 4, 2); Array.Reverse(guidBytes, 6, 2); long timestamp = DateTime.UtcNow.Ticks / 10000L; byte[] timestampBytes = BitConverter.GetBytes(timestamp); if (BitConverter.IsLittleEndian) { Array.Reverse(timestampBytes); } Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6); return new SqlGuid(guidBytes); } } [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)] private static extern int UuidCreateSequential(out Guid guid); }

 

編譯生成DLL后,注冊到數據庫

--設置數據庫是可信任
 ALTER DATABASE TEST SET TRUSTWORTHY ON

 --創建程序集
 CREATE ASSEMBLY SQLCLR FROM 'D:\SQLCLR.DLL'
 WITH PERMISSION_SET = UNSAFE


 --用程序集方法創建函數
 CREATE FUNCTION func_NewSequentialGuid()
    RETURNS uniqueidentifier
AS external name SQLCLR.FunctionNewGuid.NewSequentialGuid

 

    

測試代碼:

 批量請求:


 

select dbo.func_NewSequentialGuid() 
union
select dbo.func_NewSequentialGuid() 
union
select dbo.func_NewSequentialGuid() 
union
select dbo.func_NewSequentialGuid() 
union
select dbo.func_NewSequentialGuid() 

 結果:

 

 

 多次請求:


 

create table #t
(
    uuid uniqueidentifier 
    ,id int identity
)
go

insert into #t(uuid)
values(dbo.func_NewSequentialGuid())
go 10

select * from #t

 

 

 

git地址

https://gitee.com/wangzhanbo/cms/tree/master/Library

 

如果有問題,希望大家指正。。。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM