【问题标题】:8 bytes for timestamp or 6 bytes for timestamp at COMB GUID at SQLServerSQL Server 中 COMB GUID 处的时间戳为 8 个字节或时间戳为 6 个字节
【发布时间】:2014-04-09 17:29:25
【问题描述】:

感谢精彩的文章The Cost of GUIDs as Primary Keys,我们有了 COMB GUID。根据目前的实现,有两种方法:

  1. 使用最后 6 个字节作为时间戳:GUIDs as fast primary keys under multiple databases
  2. 通过使用 windows 刻度使用最后 8 个字节作为时间戳:GUID COMB strategy in EF4.1 (CodeFirst)

我们都知道,对于 GUID 的 6 字节时间戳,随机字节会有更多字节,以减少 GUID 的冲突。但是,将创建更多具有相同时间戳的 GUID,并且这些 GUID 根本不是连续的。这样,8 字节的时间戳将是首选。

所以这似乎是一个艰难的选择。根据上面GUIDs as fast primary keys under multiple databases的文章,它说:

在我们继续之前,关于这种方法的简短脚注:使用 1 毫秒分辨率的时间戳意味着非常接近生成的 GUID 可能具有相同的时间戳值,因此不会是连续的。这对于某些应用程序来说可能很常见,实际上我尝试了一些替代方法,例如使用更高分辨率的计时器,例如 System.Diagnostics.Stopwatch,或者将时间戳与可以保证顺序的“计数器”结合使用一直持续到时间戳更新。然而,在测试过程中,我发现这根本没有明显的区别,即使在同一个一毫秒窗口内生成了数十甚至数百个 GUID。这也与 Jimmy Nilsson 在测试 COMB 时遇到的情况一致

只是想知道了解数据库内部的人是否可以分享一些关于上述观察的信息。是因为该数据库服务器只是将数据存储在内存中,并且只有在达到某个阈值时才写入磁盘?因此,具有相同时间戳的非序列 GUID 的插入数据的重新排序通常会发生在内存中,因此性能损失最小。

更新: 根据我们的测试,与随机 GUID 相比,COMB GUID 无法减少在 Internet 上声称的表碎片。现在似乎唯一的方法是使用 SQL Server 生成顺序 GUID。

【问题讨论】:

  • 我认为列出的所有文章都将主键聚集索引键混淆了。 GUIDS 可以很好地用作主键,尤其是在多主键的情况下,但不能很好地用作聚集索引键(尽管“不能很好地工作”取决于表中还有哪些其他列)。
  • 是的,你是对的。我们主要担心的是,由于 GUID 的随机性,它会在我们的表中聚集 PK,从而产生大量碎片。关于我上面关于同一时间戳内随机 GUID 性能的问题有什么想法吗?
  • 如果没有其他列可用作聚集索引键,那么我会选择newsequentialid()(来自下面的@ErikE)。

标签: sql-server database guid


【解决方案1】:

您引用的文章是 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%

在只有一个 4 字节的 int 列的非聚集索引中(一种常见情况),将聚集索引切换为 int 会使其缩小 60%!这直接转化为表上任何扫描的 60% 性能提升 - 这是保守的,因为行越小,页面拆分发生的频率就越低,并且碎片会保持更长的时间。

即使在聚集索引本身中,仍然有 7.2% 的性能提升,这根本不算什么。

如果您在整个数据库中使用GUIDs,其中的表具有与此类似的配置文件,切换到int 会产生 16.5% 的大小减少,而数据库本身的大小为 1.397 TB,该怎么办?您的整个数据库将大 230 Gb(请参阅 Total 列,139.7 - 116.7)。这转化为现实世界中用于高可用性存储的真金白银。它会提前将您的磁盘购买计划提前,这对您公司的底线是有害的。

永远不要使用比必要更大的数据类型。这就像无缘无故地增加汽车的重量:您为此付出代价(如果不是在速度方面,那么在燃油经济性方面)。

更新

现在我知道您正在客户端代码中创建 GUID,我可以更清楚地看到您的问题的性质。如果您能够将 GUID 的创建推迟到行插入时间,这里有一种方法可以实现。

首先,为CustomerID 列设置默认值:

ALTER TABLE dbo.Customer ADD CONSTRAINT DF_Customer_CustomerID
   DEFAULT (newsequentialid()) FOR Customer;

现在您不必在任何INSERT 中指定为CustomerID 插入什么值,您的查询可能如下所示:

DECLARE @Name varchar(100) = 'Acme Spy Devices';
INSERT dbo.Customer (Name)
OUTPUT inserted.CustomerID -- a GUID
VALUES (@Name);

在这个非常简单的示例中,您已在Customer 表中插入了一个新行,并在一个查询中向客户端返回了一个包含刚刚创建的值的行集。

如果您想显式插入 VALUES (newsequentialid(), @Name),那也可以。

【讨论】:

  • 非常感谢。我们正在使用实体框架,并且正在使用代码为 PK 生成 GUID,因此我们正在寻找一些最小的代码更改来在代码中生成顺序 GUID。此时,由于代码更改过多,我们将无法回到 INT 作为 PK。如果我们使用 newsequentialid,那么生成该 GUID 会产生额外的数据库往返,对吗?
  • 正确,newsequentialid() 必须从数据库中调用。有时可以重写代码以在插入整个记录之后 获取 ID,而不必在之前获取 ID——如果是这样,则无需额外访问数据库。
  • 再次感谢。只是想知道您是否可以在不需要额外访问数据库的情况下分享一些链接或代码示例。
  • 您的磁盘空间计算没有考虑松弛空间。
  • 是的,@GreenstoneWalker,故意不准确计算实际空间使用情况(注意我说的是“完美的 100% 填充因子”),只是为了显示两者之间的合理比较列的空间要求。您如何预期“考虑闲置空间”会影响事情?您是否认为这会严重影响我的断言,即使用 16 字节而 4 字节是一个糟糕的设计选择?
猜你喜欢
  • 1970-01-01
  • 2016-07-06
  • 1970-01-01
  • 2018-06-04
  • 2018-02-10
  • 2014-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多