我刚刚阅读了blog post关于NHibernate从系统时间(Guid.Comb)创建GUID的能力,从而避免了大量的数据库碎片。您可以将其称为客户端等效于SQL Server顺序ID。
有没有办法在Linq-to-Sql项目中使用类似的策略(通过在代码中生成Guid)?
答案 0 :(得分:50)
C#(安全)代码(NHibernate Guid Comb Generator的赞美)
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
答案 1 :(得分:9)
COMB以下列方式生成:
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 );
}
答案 2 :(得分:3)
好吧,您可以手动生成Guid
。然而,Guid
的一个优点是它是不可猜测的 - 即给定记录0000-...-0005
,通常很少(来自攻击者)检查记录0000-....-0004
等。
另外 - 重新分裂?只要您对此数据有非聚集索引,我不确定这是一个问题。您通常不会在Guid
上放置聚簇索引,因此该表将是一个堆(除非您有一个单独的聚簇索引,例如IDENTITY
int)。在这种情况下,您将添加到结尾,并将新的Guid
插入到非聚集索引中。没有真正的痛苦。
(编辑)
直接使用时间的一个问题是你引入了更多的碰撞风险;你需要担心紧密循环Guid
创建(即在按顺序创建一些时避免重复),这意味着同步等等 - 如果多台机器并行工作,它会变得更加麻烦 - 很有可能你会得到重复的。
答案 3 :(得分:3)
你总是可以调用UuidCreateSequential;这是'旧的'guid生成器(2000年之前,MSFT将其改为我们今天习惯的更随机的guid)。他们将旧的UuidCreate重命名为UuidCreateSequential,并将他们的新guid生成器放入UuidCreate的新实现中。 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();
}
}
}
}
答案 4 :(得分:2)
@arul, @Doug
为什么你把时间部分放在GUID的末尾?
我认为前导字节对于排序来说更重要, 和排序是为什么首先引入时间部分以防止索引碎片。
好的,我找到了answer,这个answer from Bernhard Kircher以及他引用的网站Comparing GUID and uniqueidentifier Values (ADO.NET)。
因此,以这种方式生成的GUID在MS SQL-Server之外的其他数据库上的工作方式不同,但这与LINQ-to-SQL无关。对于已变形的网址感到抱歉,但我没有足够的声誉来发布更多链接。
答案 5 :(得分:0)
我们首先使用与Doug在Entity Framework模型中发布的方法类似的方法,因此您必须能够使用Linq to SQL来执行此操作。
在这样做的同时,我们需要一个梳子guid发生器进行测试,最终建立了这个小工具来在线生成梳子guids
http://www.webdesigncompany.co.uk/comb-guid/
希望它也能帮助你。