感谢精彩的文章The Cost of GUIDs as Primary Keys,我们有了COMB GUID。根据目前的实施情况,有两种方法:
我们都知道,对于GUID的6字节时间戳,随机字节会有更多的字节来减少GUID的冲突。但是,将创建具有相同时间戳的更多GUID,并且这些GUID根本不是连续的。有了这个,8字节时间戳将是首选。
所以这似乎是一个艰难的选择。根据上面GUIDs as fast primary keys under multiple databases的文章,它说:
在我们继续之前,有一个关于这种方法的简短脚注:使用1毫秒分辨率的时间戳意味着非常靠近的GUID可能具有相同的时间戳值,因此不会是顺序的。这可能是某些应用程序的常见现象,实际上我尝试了一些替代方法,例如使用更高分辨率的计时器,如System.Diagnostics.Stopwatch,或将时间戳与“计数器”相结合,以保证序列一直持续到时间戳更新。但是,在测试过程中,我发现即使在同一个1毫秒的窗口内生成了数十甚至数百个GUID,这根本没有明显区别。这与Jimmy Nilsson在使用COMB进行测试时遇到的情况一致
想知道是否有人知道数据库内部可以分享关于上述观察的一些灯光。是因为数据库服务器只是将数据存储在内存中,只有在达到某个阈值时才写入磁盘?因此,具有相同时间戳的非序列GUID的插入数据的重新排序通常会在内存中发生,因此性能损失最小。
更新 根据我们的测试,与随机GUID相比,COMB GUID无法减少因互联网声称的表碎片。现在似乎唯一的方法是使用SQL Server生成顺序GUID。
答案 0 :(得分:2)
您引用的文章来自2002年,而且非常古老。只需使用newsequentialid(在SQL Server 2005及更高版本中可用)。这可以保证您生成的每个新ID都大于前一个,从而解决索引碎片/页面拆分问题。
我想提到的另一个方面是,那篇文章的作者掩盖了,当你只需要4时使用16个字节并不是一个好主意。假设您有一个包含500,000行的表,平均150个字节,不包括聚簇列,并且该表有3个非聚簇索引(在每行中重复聚簇列),每个索引依次包含4个字节,25个字节和50个行字节不计算聚簇列。
然后是完美100%填充因子的存储要求(所有数字均以兆字节为单位,除非%):
Item Clust 50 25 4 Total
---- ----- ----- ----- ----- ------
GUID 79.1 31.5 19.6 9.5 139.7
int 73.4 25.7 13.8 3.8 116.7
%imp 7.2% 18.4% 29.6% 60.0% 16.5%
在非聚集索引中只有一个int
列的4个字节(常见场景),将聚簇索引切换为int
会使其缩小60%!对于桌面上的任何扫描,这直接转化为60%的性能提升 - 这是保守的,因为行数较少时,页面拆分的频率会降低,碎片会保持更长时间。
即使在聚集索引本身,仍然有7.2%的性能提升,这根本不是什么。
如果您在整个数据库中使用了GUID
,该数据库具有与此类似的配置文件,那么切换到int
将导致大小减少16.5%,数据库本身为1.397太字节大小?您的整个数据库将增加230 Gb(请参阅Total column,139.7 - 116.7)。这可以转化为现实世界中用于高可用性存储的真钱。它会提前及早移动您的磁盘购买计划,这对您公司的利润有害。
不要使用比必要更大的数据类型。这就像是无缘无故地为你的汽车增加重量:你将支付它(如果没有速度,那么在燃油经济性方面)。
<强>更新强>
现在我知道您在客户端代码中创建了GUID,我可以更清楚地看到问题的本质。如果 能够推迟创建GUID直到行插入时间,这是实现此目的的一种方法。
首先,为CustomerID
列设置默认值:
ALTER TABLE dbo.Customer ADD CONSTRAINT DF_Customer_CustomerID
DEFAULT (newsequentialid()) FOR Customer;
现在您不必在任何CustomerID
中指定要为INSERT
插入的值,您的查询可能如下所示:
DECLARE @Name varchar(100) = 'Acme Spy Devices';
INSERT dbo.Customer (Name)
OUTPUT inserted.CustomerID -- a GUID
VALUES (@Name);
在这个非常简单的示例中,您已向Customer
表中插入了一个新行,并在一个查询中将一个行集返回给包含刚刚创建的值的客户端。
如果您想明确插入可行的VALUES (newsequentialid(), @Name)
。