54

有没有办法获得 Sql Server 2005+ Sequential Guid 生成器的功能,而无需插入记录以在往返途中读回或调用本机 win dll 调用?我看到有人以使用 rpcrt4.dll 的方式回答,但我不确定这是否能够在我的托管环境中用于生产。

编辑:使用@John Boker 的答案,我试图将它变成更多的 GuidComb 生成器,而不是依赖于最后生成的 Guid 而不是重新开始。那是为了种子,而不是从我使用的 Guid.Empty 开始

public SequentialGuid()
{
    var tempGuid = Guid.NewGuid();
    var bytes = tempGuid.ToByteArray();
    var time = DateTime.Now;
    bytes[3] = (byte) time.Year;
    bytes[2] = (byte) time.Month;
    bytes[1] = (byte) time.Day;
    bytes[0] = (byte) time.Hour;
    bytes[5] = (byte) time.Minute;
    bytes[4] = (byte) time.Second;
    CurrentGuid = new Guid(bytes);
}

我基于评论

// 3 - the least significant byte in Guid ByteArray 
        [for SQL Server ORDER BY clause]
// 10 - the most significant byte in Guid ByteArray 
        [for SQL Server ORDERY BY clause]
SqlOrderMap = new[] {3, 2, 1, 0, 5, 4, 7, 6, 9, 8, 15, 14, 13, 12, 11, 10};

这看起来像我想用 DateTime 为 guid 播种的方式,还是看起来我应该反向执行并从 SqlOrderMap 索引的末尾向后工作?我不太担心它们会在任何时候创建初始 guid 时出现分页中断,因为它只会在应用程序回收期间发生。

4

11 回答 11

76

您可以只使用 SQL Server 使用的相同Win32 API 函数

UuidCreateSequential

并应用一些位移来将值按大端顺序排列。

既然你想在 C# 中使用它:

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

public static Guid NewSequentialID()
{
   //Code is released into the public domain; no attribution required
   const int RPC_S_OK = 0;

   Guid guid;
   int result = NativeMethods.UuidCreateSequential(out guid);
   if (result != RPC_S_OK)
      return Guid.NewGuid();

   //Endian swap the UInt32, UInt16, and UInt16 into the big-endian order (RFC specified order) that SQL Server expects
   //See https://stackoverflow.com/a/47682820/12597
   //Short version: UuidCreateSequential writes out three numbers in litte, rather than big, endian order
   var s = guid.ToByteArray();
   var t = new byte[16];

   //Endian swap UInt32
   t[3] = s[0];
   t[2] = s[1];
   t[1] = s[2];
   t[0] = s[3];
   //Endian swap UInt16
   t[5] = s[4];
   t[4] = s[5];
   //Endian swap UInt16
   t[7] = s[6];
   t[6] = s[7];
   //The rest are already in the proper order
   t[8] = s[8];
   t[9] = s[9];
   t[10] = s[10];
   t[11] = s[11];
   t[12] = s[12];
   t[13] = s[13];
   t[14] = s[14];
   t[15] = s[15];

   return new Guid(t);
}

也可以看看


微软UuidCreateSequential只是一个类型 1 uuid的实现RFC 4122

uuid 具有三个重要部分:

  • node: (6 bytes) - 计算机的 MAC 地址
  • timestamp: (7 bytes) - 自 1582 年 10 月 15 日 00:00:00.00 (公历改革为基督教历法的日期)以来的 100 ns 间隔数
  • clockSequenceNumber(2 个字节) - 如果您生成 guid 的速度超过 100ns,或者您更改了您的 mac 地址,则计数器

基本算法是:

  1. 获得系统范围的锁
  2. 读取最后一个node,timestampclockSequenceNumber从持久存储(注册表/文件)
  3. 获取当前node(即MAC地址)
  4. 获取当前timestamp
    • a) 如果保存的状态不可用或已损坏,或者 mac 地址已更改,则生成随机clockSequenceNumber
    • b) 如果状态可用,但当前timestamp的时间戳与保存的时间戳相同或更早,则递增clockSequenceNumber
  5. 保存nodetimestamp然后clockSequenceNumber返回持久存储
  6. 释放全局锁
  7. 根据 rfc 格式化 guid 结构

有一个 4 位版本号和 2 位变体,也需要与数据进行“与”运算:

guid = new Guid(
      timestamp & 0xFFFFFFFF,  //timestamp low
      (timestamp >> 32) & 0xFFFF, //timestamp mid
      ((timestamp >> 40) & 0x0FFF), | (1 << 12) //timestamp high and version (version 1)
      (clockSequenceNumber & 0x3F) | (0x80), //clock sequence number and reserved
      node[0], node[1], node[2], node[3], node[4], node[5], node[6]);

注意:完全未经测试;我只是从 RFC 中观察到它。

于 2012-03-02T19:00:12.450 回答
27

这个人想出了一些东西来制作顺序指南,这是一个链接

http://developmenttips.blogspot.com/2008/03/generate-sequential-guids-for-sql.html

相关代码:

public class SequentialGuid {
    Guid _CurrentGuid;
    public Guid CurrentGuid {
        get {
            return _CurrentGuid;
        }
    }

    public SequentialGuid() {
        _CurrentGuid = Guid.NewGuid();
    }

    public SequentialGuid(Guid previousGuid) {
        _CurrentGuid = previousGuid;
    }

    public static SequentialGuid operator++(SequentialGuid sequentialGuid) {
        byte[] bytes = sequentialGuid._CurrentGuid.ToByteArray();
        for (int mapIndex = 0; mapIndex < 16; mapIndex++) {
            int bytesIndex = SqlOrderMap[mapIndex];
            bytes[bytesIndex]++;
            if (bytes[bytesIndex] != 0) {
                break; // No need to increment more significant bytes
            }
        }
        sequentialGuid._CurrentGuid = new Guid(bytes);
        return sequentialGuid;
    }

    private static int[] _SqlOrderMap = null;
    private static int[] SqlOrderMap {
        get {
            if (_SqlOrderMap == null) {
                _SqlOrderMap = new int[16] {
                    3, 2, 1, 0, 5, 4, 7, 6, 9, 8, 15, 14, 13, 12, 11, 10
                };
                // 3 - the least significant byte in Guid ByteArray [for SQL Server ORDER BY clause]
                // 10 - the most significant byte in Guid ByteArray [for SQL Server ORDERY BY clause]
            }
            return _SqlOrderMap;
        }
    }
}
于 2009-11-17T21:39:23.250 回答
20

下面是 NHibernate 是如何实现 Guid.Comb 算法的:

private Guid GenerateComb()
{
    byte[] guidArray = Guid.NewGuid().ToByteArray();

    DateTime baseDate = new DateTime(1900, 1, 1);
    DateTime now = DateTime.UtcNow;

    // Get the days and milliseconds which will be used to build the byte string 
    TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);
    TimeSpan msecs = now.TimeOfDay;

    // Convert to a byte array 
    // Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
    byte[] daysArray = BitConverter.GetBytes(days.Days);
    byte[] msecsArray = BitConverter.GetBytes((long) (msecs.TotalMilliseconds / 3.333333));

    // Reverse the bytes to match SQL Servers ordering 
    Array.Reverse(daysArray);
    Array.Reverse(msecsArray);

    // Copy the bytes into the guid 
    Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
    Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);

    return new Guid(guidArray);
}
于 2014-08-24T14:41:21.937 回答
7

可以在此处找到经常更新(每毫秒至少 3 次)的顺序指南。它是使用常规 C# 代码(无本机代码调用)创建的。

于 2013-11-01T21:04:22.400 回答
6

与其他建议进行比较可能很有趣:

EntityFramework Core 还实现了一个顺序GuidValueGenerator。它们为每个值生成随机 guid,并且仅根据时间戳和线程安全增量更改最重要的字节,以便在 SQL Server 中进行排序。

源链接

这导致值都非常不同,但时间戳可排序。

于 2016-10-03T15:52:14.663 回答
4

C#版本

    public static Guid ToSeqGuid()
    {
        Int64 lastTicks = -1;
        long ticks = System.DateTime.UtcNow.Ticks;

        if (ticks <= lastTicks)
        {
            ticks = lastTicks + 1;
        }

        lastTicks = ticks;

        byte[] ticksBytes = BitConverter.GetBytes(ticks);

        Array.Reverse(ticksBytes);

        Guid myGuid = new Guid();
        byte[] guidBytes = myGuid.ToByteArray();

        Array.Copy(ticksBytes, 0, guidBytes, 10, 6);
        Array.Copy(ticksBytes, 6, guidBytes, 8, 2);

        Guid newGuid = new Guid(guidBytes);

        string filepath = @"C:\temp\TheNewGuids.txt";
        using (StreamWriter writer = new StreamWriter(filepath, true))
        {
            writer.WriteLine("GUID Created =  " + newGuid.ToString());
        }

        return newGuid;

    }

}

}

于 2013-06-05T23:11:36.457 回答
3

我的解决方案(在 VB 中但易于转换)。它将 GUID 的最重要(用于 SQL Server 排序)的前 8 个字节更改为 DateTime.UtcNow.Ticks,并且如果您比系统更快地调用新的 GUID,它还有额外的代码来帮助解决多次获取相同 Ticks 的问题时钟更新。

Private ReadOnly _toSeqGuidLock As New Object()
''' <summary>
''' Replaces the most significant eight bytes of the GUID (according to SQL Server ordering) with the current UTC-timestamp.
''' </summary>
''' <remarks>Thread-Safe</remarks>
<System.Runtime.CompilerServices.Extension()> _
Public Function ToSeqGuid(ByVal guid As Guid) As Guid

    Static lastTicks As Int64 = -1

    Dim ticks = DateTime.UtcNow.Ticks

    SyncLock _toSeqGuidLock

        If ticks <= lastTicks Then
            ticks = lastTicks + 1
        End If

        lastTicks = ticks

    End SyncLock

    Dim ticksBytes = BitConverter.GetBytes(ticks)

    Array.Reverse(ticksBytes)

    Dim guidBytes = guid.ToByteArray()

    Array.Copy(ticksBytes, 0, guidBytes, 10, 6)
    Array.Copy(ticksBytes, 6, guidBytes, 8, 2)

    Return New Guid(guidBytes)

End Function
于 2013-02-26T10:45:20.737 回答
3

没有特别指导,但我现在通常使用雪花风格的顺序 id 生成器。与顺序 guid 相比,具有与 guid 相同的优点,同时具有更好的聚集索引兼容性。

.NET Core 的 Flakey

.NET 框架的 IdGen

于 2016-10-03T20:37:29.133 回答
3

我刚刚接受了Moslem Ben Dhaou的基于 NHibernate 的回答,并将其作为扩展功能:

using System;

namespace Atlas.Core.Kernel.Extensions
{
  public static class Guids
  {
    public static Guid Comb(this Guid source)
    {
      byte[] guidArray = source.ToByteArray();

      DateTime baseDate = new DateTime(1900, 1, 1);
      DateTime now = DateTime.Now;

      // Get the days and milliseconds which will be used to build the byte string 
      TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);
      TimeSpan msecs = now.TimeOfDay;

      // Convert to a byte array 
      // Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
      byte[] daysArray = BitConverter.GetBytes(days.Days);
      byte[] msecsArray = BitConverter.GetBytes((long)(msecs.TotalMilliseconds / 3.333333));

      // Reverse the bytes to match SQL Servers ordering 
      Array.Reverse(daysArray);
      Array.Reverse(msecsArray);

      // Copy the bytes into the guid 
      Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
      Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);

      return new Guid(guidArray);
    }
  }
}
于 2017-02-24T23:47:01.353 回答
2

据我所知 NHibernate 有特殊的生成器,称为 GuidCombGenerator。你可以看看它。

于 2009-11-17T21:53:35.137 回答
2

我刚看到这个问题……我碰巧是一个用于生成 COMB 样式 GUID 的小型开源 .NET 库的作者。

该库支持原始方法(与 SQL Server 的datetime类型兼容)和使用 Unix 时间戳的方法,它们具有更高的时间精度。它还包括一个更适合 PostgrSQL 的变体:

https://github.com/richardtallent/RT.Comb

于 2017-07-05T04:18:51.807 回答