连续的GUIDs

15

我希望有人能回答这个问题。
rpcrt4.dll类中的UuidCreateSequential方法是如何使用种子生成GUID的?
我知道这么多:
微软更改了UuidCreate函数,因此它不再将机器的MAC地址作为UUID的一部分。由于CoCreateGuid调用UuidCreate以获取其GUID,因此其输出也发生了变化。如果您仍然喜欢按顺序生成GUID(有助于在系统注册表中保持相关的GUID组在一起),则可以使用UuidCreateSequential函数。
问题背后的原因是:如果我在Web集群中使用此函数生成顺序GUID,如何确保GUID接近GUID范围,而不会出现GUID重复的潜在风险?

这是一个7年前的问题。但是问题不同,这里给出的答案更加深入。 - Sean
5个回答

33

Win32的UuidCreateSequential创建一个版本1UUID

以下是使用UuidCreateSequential在我的计算机上创建的一些示例版本1 UUID:

GuidToString                            Raw bytes
======================================  =================================================
{1BE8D85D-63D1-11E1-80DB-B8AC6FBE26E1}  1B E8 D8 5D 63 D1 11 E1  80 DB  B8 AC 6F BE 26 E1
{1BE8D85E-63D1-11E1-80DB-B8AC6FBE26E1}  1B E8 D8 5E 63 D1 11 E1  80 DB  B8 AC 6F BE 26 E1
{1BE8D85F-63D1-11E1-80DB-B8AC6FBE26E1}  1B E8 D8 5F 63 D1 11 E1  80 DB  B8 AC 6F BE 26 E1
{1BE8D860-63D1-11E1-80DB-B8AC6FBE26E1}  1B E8 D8 60 63 D1 11 E1  80 DB  B8 AC 6F BE 26 E1
{1BE8D861-63D1-11E1-80DB-B8AC6FBE26E1}  1B E8 D8 61 63 D1 11 E1  80 DB  B8 AC 6F BE 26 E1
{1BE8D862-63D1-11E1-80DB-B8AC6FBE26E1}  1B E8 D8 62 63 D1 11 E1  80 DB  B8 AC 6F BE 26 E1
{1BE8D863-63D1-11E1-80DB-B8AC6FBE26E1}  1B E8 D8 63 63 D1 11 E1  80 DB  B8 AC 6F BE 26 E1
{1BE8D864-63D1-11E1-80DB-B8AC6FBE26E1}  1B E8 D8 64 63 D1 11 E1  80 DB  B8 AC 6F BE 26 E1
{1BE8D865-63D1-11E1-80DB-B8AC6FBE26E1}  1B E8 D8 65 63 D1 11 E1  80 DB  B8 AC 6F BE 26 E1
{220FB46C-63D1-11E1-80DB-B8AC6FBE26E1}  22 0F B4 6C 63 D1 11 E1  80 DB  B8 AC 6F BE 26 E1

首先需要注意的是,这些 UUID 包含了我的机器 MAC 地址(B8AC6FBE26E1):

enter image description here

                        Node
======================= ============
1BE8D85D-63D1-11E1-80DB B8AC6FBE26E1
1BE8D85E-63D1-11E1-80DB B8AC6FBE26E1
1BE8D85F-63D1-11E1-80DB B8AC6FBE26E1
1BE8D860-63D1-11E1-80DB B8AC6FBE26E1
1BE8D861-63D1-11E1-80DB B8AC6FBE26E1
1BE8D862-63D1-11E1-80DB B8AC6FBE26E1
1BE8D863-63D1-11E1-80DB B8AC6FBE26E1
1BE8D864-63D1-11E1-80DB B8AC6FBE26E1
1BE8D865-63D1-11E1-80DB B8AC6FBE26E1
220FB46C-63D1-11E1-80DB B8AC6FBE26E1

如果你希望不同的计算机生成靠近彼此的GUID,那么你会感到失望。

让我们看看剩下的值。

剩下的10字节中有七个半字节是时间戳;即从1582年10月15日 00:00:00以来的100纳秒间隔数。将这些时间戳字节重新排列组合:

Timestamp              Node
=============== ====== ============
1E163D11BE8D85D 1-80DB B8AC6FBE26E1
1E163D11BE8D85E 1-80DB B8AC6FBE26E1
1E163D11BE8D85F 1-80DB B8AC6FBE26E1
1E163D11BE8D860 1-80DB B8AC6FBE26E1
1E163D11BE8D861 1-80DB B8AC6FBE26E1
1E163D11BE8D862 1-80DB B8AC6FBE26E1
1E163D11BE8D863 1-80DB B8AC6FBE26E1
1E163D11BE8D864 1-80DB B8AC6FBE26E1
1E163D11BE8D865 1-80DB B8AC6FBE26E1
1E163D1220FB46C 1-80DB B8AC6FBE26E1

通过使用 UuidCreateSequential在同一台机器上创建的GUID将会被放在一起,因为它们是按照时间顺序生成的。


1 是版本号,在这种情况下意味着一个基于时间的UUID。共定义了5个版本:

即:

Timestamp       Version      Node
=============== ======= ==== ============
1E163D11BE8D85D 1       80DB B8AC6FBE26E1
1E163D11BE8D85E 1       80DB B8AC6FBE26E1
1E163D11BE8D85F 1       80DB B8AC6FBE26E1
1E163D11BE8D860 1       80DB B8AC6FBE26E1
1E163D11BE8D861 1       80DB B8AC6FBE26E1
1E163D11BE8D862 1       80DB B8AC6FBE26E1
1E163D11BE8D863 1       80DB B8AC6FBE26E1
1E163D11BE8D864 1       80DB B8AC6FBE26E1
1E163D11BE8D865 1       80DB B8AC6FBE26E1
1E163D1220FB46C 1       80DB B8AC6FBE26E1

最后一个单词包含两个内容。

其中低12位是机器特定的Clock Sequence号码:

Timestamp       Version   Clock Sequence   Node
=============== ======= = ================ ============
1E163D11BE8D85D 1       8 0DB              B8AC6FBE26E1
1E163D11BE8D85E 1       8 0DB              B8AC6FBE26E1
1E163D11BE8D85F 1       8 0DB              B8AC6FBE26E1
1E163D11BE8D860 1       8 0DB              B8AC6FBE26E1
1E163D11BE8D861 1       8 0DB              B8AC6FBE26E1
1E163D11BE8D862 1       8 0DB              B8AC6FBE26E1
1E163D11BE8D863 1       8 0DB              B8AC6FBE26E1
1E163D11BE8D864 1       8 0DB              B8AC6FBE26E1
1E163D11BE8D865 1       8 0DB              B8AC6FBE26E1
1E163D1220FB46C 1       8 0DB              B8AC6FBE26E1

如果发生以下情况,这个机器范围内的持久值就会增加:

  • 您更换了网络卡
  • 您在上一个UUID生成后的100 ns内生成一个新的UUID(并且时间戳会冲突)

因此,由 UuidCreateSequential 创建的任何 GUID 都将(理想情况下)具有相同的 Clock Sequence 号码,使它们“相近”。

最后2位被称为 Variant,始终设置为二进制 10

Timestamp       Version Variant Clock Sequence   Node
=============== ======= ======= ================ ============
1E163D11BE8D85D 1       8       0DB              B8AC6FBE26E1
1E163D11BE8D85E 1       8       0DB              B8AC6FBE26E1
1E163D11BE8D85F 1       8       0DB              B8AC6FBE26E1
1E163D11BE8D860 1       8       0DB              B8AC6FBE26E1
1E163D11BE8D861 1       8       0DB              B8AC6FBE26E1
1E163D11BE8D862 1       8       0DB              B8AC6FBE26E1
1E163D11BE8D863 1       8       0DB              B8AC6FBE26E1
1E163D11BE8D864 1       8       0DB              B8AC6FBE26E1
1E163D11BE8D865 1       8       0DB              B8AC6FBE26E1
1E163D1220FB46C 1       8       0DB              B8AC6FBE26E1

所以这就是它的全部。顺序GUID是连续的;如果您在同一台计算机上创建它们,则它们将在数据库中“接近”彼此。


但是您想知道在不同计算机上创建两个顺序UUID时实际发生了什么。

使用我们对版本1 GUID的新发现知识,让我们构造来自不同计算机的相同时间戳的两个GUID,例如:

{1BE8D85D-63D1-11E1-80DB-B8AC6FBE26E1}
{1BE8D85D-63D1-11E1-80DB-123456789ABC}

首先,让我们插入一堆带有连续时间戳的GUID。首先创建一个临时表来存储我们的GUID,并按照GUID进行集群

--DROP table #uuidOrderingTest
CREATE TABLE #uuidOrderingTest
( 
    uuid uniqueidentifier not null
)

CREATE clustered index IX_uuidorderingTest_uuid ON #uuidOrderingTest 
( 
   uuid
)

现在插入数据:

INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D866-63D1-11E1-80DB-B8AC6FBE26E1}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D862-63D1-11E1-80DB-B8AC6FBE26E1}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D861-63D1-11E1-80DB-B8AC6FBE26E1}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D85E-63D1-11E1-80DB-B8AC6FBE26E1}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D864-63D1-11E1-80DB-B8AC6FBE26E1}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D863-63D1-11E1-80DB-B8AC6FBE26E1}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D85F-63D1-11E1-80DB-B8AC6FBE26E1}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D85D-63D1-11E1-80DB-B8AC6FBE26E1}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D865-63D1-11E1-80DB-B8AC6FBE26E1}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D860-63D1-11E1-80DB-B8AC6FBE26E1}')

注意: 我将它们随机插入以展示 SQL Server 的聚簇效果。

获取这些行并查看它们按照顺序排列(时间戳顺序)的顺序:

SELECT * FROM #uuidOrderingTest

uuid
------------------------------------
1BE8D85D-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D85E-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D85F-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D860-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D861-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D862-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D863-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D864-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D865-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D866-63D1-11E1-80DB-B8AC6FBE26E1

现在让我们使用以下方式插入guid:

  • 具有相同的时间戳
  • 但是不同的节点(即MAC地址):

从“不同”的计算机中插入新的guid:

INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D866-63D1-11E1-80DB-123456789ABC}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D862-63D1-11E1-80DB-123456789ABC}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D861-63D1-11E1-80DB-123456789ABC}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D85E-63D1-11E1-80DB-123456789ABC}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D864-63D1-11E1-80DB-123456789ABC}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D863-63D1-11E1-80DB-123456789ABC}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D85F-63D1-11E1-80DB-123456789ABC}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D85D-63D1-11E1-80DB-123456789ABC}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D865-63D1-11E1-80DB-123456789ABC}')
INSERT INTO #uuidOrderingTest (uuid) VALUES ('{1BE8D860-63D1-11E1-80DB-123456789ABC}')

获取结果:

uuid
------------------------------------
1BE8D85D-63D1-11E1-80DB-123456789ABC
1BE8D85E-63D1-11E1-80DB-123456789ABC
1BE8D85F-63D1-11E1-80DB-123456789ABC
1BE8D860-63D1-11E1-80DB-123456789ABC
1BE8D861-63D1-11E1-80DB-123456789ABC
1BE8D862-63D1-11E1-80DB-123456789ABC
1BE8D863-63D1-11E1-80DB-123456789ABC
1BE8D864-63D1-11E1-80DB-123456789ABC
1BE8D865-63D1-11E1-80DB-123456789ABC
1BE8D866-63D1-11E1-80DB-123456789ABC
1BE8D85D-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D85E-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D85F-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D860-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D861-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D862-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D863-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D864-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D865-63D1-11E1-80DB-B8AC6FBE26E1
1BE8D866-63D1-11E1-80DB-B8AC6FBE26E1

就是这样,SQL Server对Node排序优先于Timestamp。来自不同机器的Uuid将不会聚集在一起。如果它没有这样做会更好,但你能怎么办呢。


Zalgo是否分配您的DHCP地址? - James McLachlan
@JamesMcLachlan 我的Windows配置的语言环境与en-us不同(https://dev59.com/uGw05IYBdhLWcg3wxUj_#7042940)。你看到的日期格式是非美式英语环境下的。 - Ian Boyd

3

我通常使用自己的序列化 guid,而不是依赖于 Win32 API。这个序列化 guid 将标准 guid 的八个字节替换为来自日期时间的 ticks。

var guidBinary = new byte[16];
Array.Copy( Guid.NewGuid().ToByteArray(), 0, guidBinary, 0, 8 );
Array.Copy( BitConverter.GetBytes( DateTime.Now.Ticks ), 0, guidBinary, 8, 8 );
return new Guid( guidBinary );

那么在OP的情况下,他们可以使这8个字节连续吗? - Robert Harvey
@Robert Harvey - 或者六个或四个。这取决于他们想要的独特性和连续性的平衡。 - Thomas
@Robert Harvey - (显然,如果要得到除我在这里使用的八以外的其他东西,他们必须调整例程。) - Thomas
这在 SQL Server 2017 上不会产生连续的 GUID。 - twoflower
@twoflower - 是的,它是这样的。微软没有改变二进制在不同版本的SQL Server中的解释方式。 - Thomas

0

不确定 Win32 的方式,但如果您具有 MSSQL 数据库连接,您可以使用“未记录的” 'newSequentialID()'。

我说“未记录的”是因为当尝试将其保存为 MSSQL Identity 列的默认值时,它被视为错误,并且您必须覆盖它并声明要使用它。


我知道SQL中的newsequentialId()。但是,我们正在尝试查看使用C#创建顺序GUID与使用SQL Server的优缺点... 那么,如果我们有一个集群Web服务器并且我们使用代码生成GUID,当有多个服务器执行相同操作时,它会如何影响它,并且如何保持GUID紧密顺序而不重复? - Sean
此外,在默认约束之外,您不能使用NewSequentialGuid()函数。 - Thomas
@Thomas:这是已知的;您可以使用表格创建不重复的顺序ID。 - NKCSS
使用顺序 GUID 作为 ID 在插入时更快,因为您将数据添加到索引的末尾而不是某个随机页面,当您拥有较大的表格时,这变得非常重要。 - Walter Verhoeven

0

您可以拥有一个中央表格,其中包含范围的最后已知起始UID并递增它。

例如:DB1创建一个GUID {AA333F14-FCCD-4bee-9F8F-9D9BDF1B8766} 并将其写入表格。DB2上线并看到 {AA333F14-FCCD-4bee-9F8F-9D9BDF1B8766} 并将其递增一些设置数字,如 1,000,000,000,000,000 或其他非常高的数字,以便您不会有任何重叠值。

但是,当按顺序使用时,GUID几乎没有用处。

我想真正的问题是,您要使用GUID做什么?如果您想要一个递增的数字,只需使用64位int(也称为bigint)


0

我修改了Thomas的答案,使前8个字节递增

 var guidBinary = new byte[16];
 Array.Copy(BitConverter.GetBytes(DateTime.Now.Ticks), 0, guidBinary, 0, 8);
 Array.Copy(Guid.NewGuid().ToByteArray(), 8, guidBinary, 8, 8);
 return new Guid(guidBinary);

结果会类似于以下内容

b0c99468-714a-08d4-88bd-39e0b53455fb
b122b4b8-714a-08d4-9b12-924e850ad2fe
b1254cf0-714a-08d4-b7c9-954d36290ce5
b12573ff-714a-08d4-b000-632c3a58874d

这在 SQL Server 2017 上不会产生连续的 GUID(在兼容性级别设置为 140 的 Azure SQL 数据库上进行了测试)。 - twoflower

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