問題描述
有序的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
如果有問題,希望大家指正。。。