Linq-to-Sql中的顺序GUID?

20

我刚刚阅读了一篇关于NHibernate能够从系统时间生成GUID(Guid.Comb),从而避免数据库碎片化的博客文章。你可以将其称为SQL Server顺序ID的客户端等效物。

在我的 Linq-to-Sql 项目中,是否有一种类似的策略可以使用(通过在代码中生成GUID)?


我尝试了所有这些COMB guid示例,它们都会在10K行时产生高碎片化。Arul的版本-98%的碎片化。<br/> NHibernate-53%的碎片化。<br/> rpcrt4.dll-98%的碎片化<br/> bigint-6%的碎片化<br/>人们对COMB商品的表现有多好期望? - Shaun
@Shaun 这取决于你插入的速度有多快,如果每秒只有几个插入操作,那么就不应该出现碎片化问题。 - Peter
6个回答

50

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


1
刚被添加到我的项目中了! - Chris Marisic
1
很高兴听到这个消息。我在NHibernate源代码中发现了这个小宝石,不得不分享一下 :) - Doug

9

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 );
    }

这对我也非常有帮助,但是我是否需要使用/unsafe编译整个程序,还是可以将其放入自己的类库中,并仅使用不安全标志编译该类库? - Refracted Paladin
@Paladin:你可以不使用不安全的代码来完成它。看一下BitConvert类。 - R. Martinho Fernandes
1
请查看下面我提供的安全版本的Guid.Comb生成答案。 - Doug

3

您可以始终调用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();
            }
        }
    }
}

2
请注意,如果您正在使用Mono,则rpcrt4.dll文件将不存在,因此这将无法正常工作。 - Doug
好观点Doug。我使用UuidCreateSequential,但忘记了这个snapful的存在。 - granadaCoder
如果机器重新启动了,该怎么办?那么您将失去顺序。 - Wahid Bitar
@WahidBitar 对于分段,这几乎无关紧要...除非你非常经常重启... - KristoferA

3

好的,您可以手动生成Guid。然而,Guid的一个优点是它不可猜测 - 即给定记录0000-...-0005,攻击者通常没有必要检查记录0000-....-0004等。

此外 - 关于分片?只要在这些数据上有一个非聚集索引,我不确定这是一个问题。你通常不会对Guid放置聚集索引,所以表将成为堆(除非你有一个单独的聚集索引,例如IDENTITY int)。在这种情况下,你将添加到末尾,并将新的Guid插入到非聚集索引中。没有真正的痛苦。

(编辑) 直接使用时间的一个问题是,你会引入更多的碰撞风险; 你需要担心紧密循环的Guid创建(即在连续创建几个时避免重复),这意味着同步等 - 如果多台机器正在并行工作,情况会变得更加麻烦 - 有重复的可能性。


我建议的正确答案是将任意Guid与时间生成的部分结合起来,这样可以消除重复的风险。但我不知道它是否解决了任何碎片化问题... - JacobE

2

@arul, @Doug

为什么你把时间部分放在GUID的末尾?

我认为前导字节对于排序更为重要,而排序是引入时间部分的原因,以防止索引碎片化。

好的,我找到了 答案,以及来自 Bernhard Kircher 的答案和他所引用的网站 Comparing GUID and uniqueidentifier Values (ADO.NET)

这种生成方式的GUID在除了 MS SQL-Server 之外的其他数据库上不能正常工作,但这与 LINQ-to-SQL 无关。

很抱歉链接变形,但我没有足够的声望来发布更多链接。


0
我们在 Entity Framework model first 中使用了与 Doug 上面发布的类似方法,所以您也必须能够使用 Linq to SQL 来完成它。
在这个过程中,我们需要一个用于测试的组合 GUID 生成器,并最终构建了这个小工具来在线生成组合 GUID。

http://www.webdesigncompany.co.uk/comb-guid/

希望这也能对你有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接