我刚刚阅读了一篇关于NHibernate能够从系统时间生成GUID(Guid.Comb),从而避免数据库碎片化的博客文章。你可以将其称为SQL Server顺序ID的客户端等效物。
在我的 Linq-to-Sql 项目中,是否有一种类似的策略可以使用(通过在代码中生成GUID)?
我刚刚阅读了一篇关于NHibernate能够从系统时间生成GUID(Guid.Comb),从而避免数据库碎片化的博客文章。你可以将其称为SQL Server顺序ID的客户端等效物。
在我的 Linq-to-Sql 项目中,是否有一种类似的策略可以使用(通过在代码中生成GUID)?
C#(安全编码)代码(感谢NHibernate Guid Comb生成器)
Guid GenerateComb()
{
byte[] destinationArray = Guid.NewGuid().ToByteArray();
DateTime time = new DateTime(0x76c, 1, 1);
DateTime now = DateTime.Now;
TimeSpan span = new TimeSpan(now.Ticks - time.Ticks);
TimeSpan timeOfDay = now.TimeOfDay;
byte[] bytes = BitConverter.GetBytes(span.Days);
byte[] array = BitConverter.GetBytes((long) (timeOfDay.TotalMilliseconds / 3.333333));
Array.Reverse(bytes);
Array.Reverse(array);
Array.Copy(bytes, bytes.Length - 2, destinationArray, destinationArray.Length - 6, 2);
Array.Copy(array, array.Length - 4, destinationArray, destinationArray.Length - 4, 4);
return new Guid(destinationArray);
}
这是指向Github的源代码链接:https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs
COMBs是以下方式生成的:
DECLARE @aGuid UNIQUEIDENTIFIER
SET @aGuid = CAST(CAST(NEWID() AS BINARY(10)) + CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER)
转化成C#后会是这个样子:
public static unsafe Guid CombGuid()
{
Guid guid = Guid.NewGuid();
byte[] bytes = guid.ToByteArray();
long ticks = DateTime.Now.Ticks;
fixed( byte* pByte = bytes )
{
int* pFirst = (int *)(pByte + 10);
short* pNext = (short*)(pByte + 14);
*pFirst = (int)(ticks & 0xFFFFFF00);
*pNext = (short)ticks;
}
return new Guid( bytes );
}
您可以始终调用UuidCreateSequential; 这是“旧”的GUID生成器(在2000年左右MSFT将其更改为我们今天使用的更随机样式的GUID之前)。他们将旧的UuidCreate重命名为UuidCreateSequential,并在新的UuidCreate实现中放置了他们的新GUID生成器。 UuidCreateSequential也是SQL Server在NewSequentialID()中使用的内容,与普通GUID一样独特,但如果您在同一进程中连续创建一堆它们,则具有顺序优势。
using System;
using System.Runtime.InteropServices;
namespace System
{
public static class GuidEx
{
[DllImport("rpcrt4.dll", SetLastError = true)]
private static extern int UuidCreateSequential(out Guid guid);
private const int RPC_S_OK = 0;
/// <summary>
/// Generate a new sequential GUID. If UuidCreateSequential fails, it will fall back on standard random guids.
/// </summary>
/// <returns>A GUID</returns>
public static Guid NewSeqGuid()
{
Guid sequentialGuid;
int hResult = UuidCreateSequential(out sequentialGuid);
if (hResult == RPC_S_OK)
{
return sequentialGuid;
}
else
{
//couldn't create sequential guid, fall back on random guid
return Guid.NewGuid();
}
}
}
}
好的,您可以手动生成Guid
。然而,Guid
的一个优点是它不可猜测 - 即给定记录0000-...-0005
,攻击者通常没有必要检查记录0000-....-0004
等。
此外 - 关于分片?只要在这些数据上有一个非聚集索引,我不确定这是一个问题。你通常不会对Guid
放置聚集索引,所以表将成为堆(除非你有一个单独的聚集索引,例如IDENTITY
int)。在这种情况下,你将添加到末尾,并将新的Guid
插入到非聚集索引中。没有真正的痛苦。
(编辑)
直接使用时间的一个问题是,你会引入更多的碰撞风险; 你需要担心紧密循环的Guid
创建(即在连续创建几个时避免重复),这意味着同步等 - 如果多台机器正在并行工作,情况会变得更加麻烦 - 有重复的可能性。
@arul, @Doug
为什么你把时间部分放在GUID的末尾?
我认为前导字节对于排序更为重要,而排序是引入时间部分的原因,以防止索引碎片化。
好的,我找到了 答案,以及来自 Bernhard Kircher 的答案和他所引用的网站 Comparing GUID and uniqueidentifier Values (ADO.NET)。
这种生成方式的GUID在除了 MS SQL-Server 之外的其他数据库上不能正常工作,但这与 LINQ-to-SQL 无关。
很抱歉链接变形,但我没有足够的声望来发布更多链接。
http://www.webdesigncompany.co.uk/comb-guid/
希望这也能对你有所帮助。