如何有顺序的生成GUID

luckbird 2010-07-01 01:44:33
想用Guid做表的主键,并在此字段上建立聚簇索引。 因为Guid是随机生成的,生成的值大小是不确定的,每次生成的数可能很大,也可能很小。这样会影响插入的效率,在网上看到有一种方法可以生成顺序的GUID,称为COMB, 描述是这样的:

--------------------------------------------------------------------

使用“COMB(Combine)”类型

  COMB数据类型的基本设计思路是这样的:保留UniqueIdentifier的前10个字节,用后6个字节表示GUID生成的时间(DateTime),这样我们将时间信息与UniqueIdentifier组合起来,在保留UniqueIdentifier的唯一性的同时增加了有序性,以此来提高索引效率。也许有人会担心UniqueIdentifier减少到10字节会造成数据出现重复,其实不用担心,后6字节的时间精度可以达到1/300秒,两个COMB类型数据完全相同的可能性是在这1/300秒内生成的两个GUID前10个字节完全相同,这几乎是不可能的!在SQL Server中用SQL命令将这一思路实现出来便是:

DECLARE @aGuid UNIQUEIDENTIFIER
SET @aGuid = CAST(CAST(NEWID() AS BINARY(10))
+ CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER)

  经过测试,使用COMB做主键比使用INT做主键,在检索、插入、更新、删除等操作上仍然显慢,但比Unidentifier类型要快上一些。

------------------------------------------------------------------------------------------------------

对于上面的实现方法在很多网站都有描述,国外的英文文章也是这样说的(估计都是从中文翻译过来的)。

但这里我有一个疑问, 上面说保留 GUID 的前10个字节,然后用当前时间作为后6个字节,组合一个新的GUID。 这样做还是没有顺序啊? 因为前10个字节有时大有时小啊。 但我搜了半天,没有人对此提出异议。我想如果把时间的6个字节加在前面是不是可以呢? 像下面这样:

DECLARE @aGuid UNIQUEIDENTIFIER
SET @aGuid = CAST(
CAST(GETDATE() AS BINARY(6))
+ CAST(NEWID() AS BINARY(10))
AS UNIQUEIDENTIFIER)

测试了一下,结果不理想,因为后面的10个字节大小不是顺序的,相加依然不是顺序的。

难道是我误入歧途了吗? 谁能指点一下啊。 谢谢了。





...全文
2926 26 打赏 收藏 转发到动态 举报
写回复
用AI写文章
26 条回复
切换为时间正序
请发表友善的回复…
发表回复
EYUANatQQdotCOM 2012-05-09
  • 打赏
  • 举报
回复
guid不是32个字符码?为什么是16个字节?两个字符是一个字节?
为什么保留前十个就可以确保不唯一?后六个字节的作用是什么?还是这样做只是为了给时间部分腾出空间?
yusheng622 2012-04-24
  • 打赏
  • 举报
回复
这帖子不赖!
luckbird 2012-02-25
  • 打赏
  • 举报
回复
[Quote=引用 21 楼 ap0405140 的回复:]

看不太懂,请教一个楼主,
"1、生成有顺序的GUID:(方法:生成一个GUID, 向右移位6个字节,然后用当前时间作为前6个字节附加上去)"

[/Quote]

抱歉,很久没有上CSDN,今天无意在google搜到这个帖子才看到。

我把我的代码贴上来:

.NET 实现的代码是:

/// <summary>
/// 获得有顺序的GUID,用Guid前10位加时间参数生成,时间加在最前面6个字节
/// 需要注意的是,在SQL SERVER数据库中,使用GUID字段类型保存的话,SQL SERVER对GUID类型字段排序算法是以最后6字节为主要依据,
/// 这与Oracle不同,为了保证排序规则与Oracle一致,在SQL SERVER中要使用Binary(16)数据类型来保存。
/// </summary>
/// <returns></returns>
public static Guid NewCombId()
{
byte[] guidArray = System.Guid.NewGuid().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.Date.Ticks - baseDate.Ticks);
TimeSpan msecs = new TimeSpan(now.Ticks - (now.Date.Ticks));

// Convert to a byte array
// 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);

for (int i = 15; i >= 6; i--)
{
guidArray[i] = guidArray[i - 6];
}

Array.Copy(daysArray, daysArray.Length - 2, guidArray, 0, 2);
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, 2, 4);

return new System.Guid(guidArray);
}

SqlServer实现的代码是:

create function [dbo].[GetCombId](@newid uniqueidentifier)
returns binary(16)
as
begin
declare @aguid binary(16)

set @aguid = cast(
cast(getdate() as binary(6))
+cast(@newid as binary(10))

as binary(16))
return @aguid
end


GO

这样就完全达到了我的要求。

对于不同服务器还是有可能会重复的可能性,我认为不会发生。因为,从原Guid去除的6位原本也是由时间参数得来的。 我们只是将其去除,并将我们自己的时间值附加到前面。只要都是我的系统产生的Guid,即使在不同的电脑上,也是不会重复的。 因为GUID的其他部分是通过网卡等参数获得的。

但是缺陷是,有可能和其他系统产生的Guid重复。


nzperfect 2011-11-07
  • 打赏
  • 举报
回复
这个想法本身就没有意义
guid可以做来做主键以保证在数据合并时不会有重复值,但并不需要用它来做聚集索引,聚集索引可以采用其它列值。
NBDBA 2011-11-06
  • 打赏
  • 举报
回复
Guid字段不适合建立聚簇索引
本来Guid字段就是个唯一码,基本无实际意义,你硬要给他次序概念,属于没事找抽型

16楼可能实现了,但是我觉得处理后可能失去原来的唯一性,不同机器同一时间产生的GUID经过处理可能增加了重复的几率,所以看起来可以,实际绝不可取。

唐诗三百首 2011-11-06
  • 打赏
  • 举报
回复
看不太懂,请教一个楼主,
"1、生成有顺序的GUID:(方法:生成一个GUID, 向右移位6个字节,然后用当前时间作为前6个字节附加上去)"

难道就是这句吗?
DECLARE @aGuid UNIQUEIDENTIFIER
SET @aGuid = CAST(CAST(NEWID() AS BINARY(10))
+ CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER)

何来"右移位6个字节,然后用当前时间作为前6个字节附加上去"这样的处理逻辑?
ws_hgo 2010-07-02
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 thinclient 的回复:]

Guid效率低,能不用还是不要用
说说你必用的理由好么?
[/Quote]
你为什么说GUID效率低
claro 2010-07-02
  • 打赏
  • 举报
回复
恭喜

鼓掌!
  • 打赏
  • 举报
回复
只要涉及在不同服务器运行生成,最后又要汇总的情况,你这些方法都无法绝对保证不会重复。虽然重复机率极少极少,但不代表就是零。

所以不能仅依靠时间,还需要加上服务器编号。
永生天地 2010-07-02
  • 打赏
  • 举报
回复
再次过来,学习
luckbird 2010-07-02
  • 打赏
  • 举报
回复
今天又测试了一下,发现对GUID字段oracle的排序规则和SqlServer又是不一样的,Oracle是用RAW(16)类型来保存的。 于是,昨天想好的解决方案又有问题了。

又经过一些分析和测试,发现SQLSERVER的BINARY(16)和Oracle的排序规则是一样的,因为都是二进制,于是决定在SQLSERVER里不使用UniqueIdentifier类型来保存GUID,而是用Binary(16)来保存,这样就和ORACLE排序规则一样了。

使用这个规则的话,之前用当前时间替换GUID后六个字节的方法就不对了,因为二进制是以第一个字节开始排序的。 这里需要改为将原来的GUID向右移位6个字节,然后将当前时间作为前6个字节。

这样的话从数据库取出时是byte[]类型,需要用下面的语句转换一下,将byte数组转为GUID.
Guid gid = new Guid(((byte[]) obj));

上面说得有点复杂了,总结一下其实就是这样的:

1、生成有顺序的GUID: (方法:生成一个GUID, 向右移位6个字节,然后用当前时间作为前6个字节附加上去)
2、在SQLSERVER中,用BINARY(16)来保存GUID,而不是UniqueIdentifier
3、将来在Oracle中用RAW(16)来保存GUID。其实就是在数据库中直接保存GUID的字节数组。





luckbird 2010-07-01
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 zhangweiit 的回复:]
“同时我的项目以后可能移植到ORACLE,不能绑死在SQLSERVER上。”
如果这样的话,楼主为何不考虑让.net来生成GUID,而不是利用SQLSERVER的内置机制
因为如果在楼主说的sqlserver转oracle的情况,或是sqlserver与oracle共存的情况下
那,,,就会出现.net,sqlserver,oracle三种不同的生成以及比较机制了
[/Quote]

是的,参照网上前辈提供的代码,已经实现了同样的算法用.NET来生成顺序的GUID,以后用ORACLE应该也没有问题了(如果ORACLE对guid的大小比较和SqlServer一致的话,应该不会不一样吧?明天搭个环境测试一下)。
haitao 2010-07-01
  • 打赏
  • 举报
回复
另外,好像看到过sql2008(?)有一种新的guid,就是有序的。。。。。。。。。
zhangweiit 2010-07-01
  • 打赏
  • 举报
回复
“同时我的项目以后可能移植到ORACLE,不能绑死在SQLSERVER上。”
如果这样的话,楼主为何不考虑让.net来生成GUID,而不是利用SQLSERVER的内置机制
因为如果在楼主说的sqlserver转oracle的情况,或是sqlserver与oracle共存的情况下
那,,,就会出现.net,sqlserver,oracle三种不同的生成以及比较机制了
haitao 2010-07-01
  • 打赏
  • 举报
回复
取 系统时间(yyyymmddhhmmsszzz)+guid 做sguid,肯定是有序而且唯一的
只是不同服务器的时间如果不准,还是会顺序不严格,但是唯一是仍然的
luckbird 2010-07-01
  • 打赏
  • 举报
回复
搞定了, DECLARE @aGuid UNIQUEIDENTIFIER
SET @aGuid = CAST(CAST(NEWID() AS BINARY(10))
+ CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER) 虽然时间是加在后6位,但实际上可能由于高低位倒置的问题,实际上是加在最前面的,这样就影响到排序了。这个方法是对的。

之前没有在 SqlServer 里对生成的结果做比较,仅从表面上来看,感觉后生成的数值小于之前生成的。作比较才发现,确实是按大小顺序生成的。

另外发现奇怪的是,对GUID类型的数据,.NET的比较和SQLSERVER的比较 是不一样的,就是说两个guid1 和 guid2 ,在.net比较,guid2<guid1. 但在SQLSERVER 里比较 guid2>guid1. 这也是比较诡异的地方,不过不影响我的使用,我只要能做到在数据库是从小到大的使用就可以了。




luckbird 2010-07-01
  • 打赏
  • 举报
回复
NEWSEQUENTIALID() 我知道,但据说他不能保证重启服务器后得到的还是大小排列的。同时我的项目以后可能移植到ORACLE,不能绑死在SQLSERVER上。

我之所以要用GUID,因为我有多个服务器同时在不同的地域使用,将来这些数据会汇总到同一个服务器上,所以要用GUID,这样不会导致主键重复。

我希望有人能帮我解答一下,
DECLARE @aGuid UNIQUEIDENTIFIER
SET @aGuid = CAST(CAST(NEWID() AS BINARY(10))
+ CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER)
这段代码,在很多网站流传,说是可以生成顺序的编码。我认为不可以,但却没有人对此提出过异议。是不是我哪里理解错了。

我不需要绝对顺序的编码,只要大多数是顺序的就可以。

永生天地 2010-07-01
  • 打赏
  • 举报
回复
[Quote=引用楼主 luckbird 的回复:]
难道是我误入歧途了吗? 谁能指点一下啊。 谢谢了。Quote]
有点
永生天地 2010-07-01
  • 打赏
  • 举报
回复
不能以 GUID 排序,没有顺序的,

一般随机取数据时用newid(),所以他是没有顺序的
wujinyuan 2010-07-01
  • 打赏
  • 举报
回复
学习了..
加载更多回复(4)

22,209

社区成员

发帖
与我相关
我的任务
社区描述
MS-SQL Server 疑难问题
社区管理员
  • 疑难问题社区
  • 尘觉
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧