【问题标题】:25k Inserts Daily, 99% Fragmentation on Clustered GUID Index每天 25k 插入,99% 的集群 GUID 索引碎片
【发布时间】:2012-02-12 07:51:20
【问题描述】:

我有一个主键作为聚集 GUID 字段的表;我正在使用NEWSEQUENTIALID() 而不是NEWID 生成GUID。不幸的是,因为这个表每天看到大约 25k-100k 的插入,所以在几个小时内(默认:聚集)主键索引变成了 99% 的碎片。

我最初使用 NEWID 而不是生成顺序 ID,但即使我重新创建表并使用 NEWSEQUENTIALID 重新插入所有行(并将其指定为主键列的默认值),我仍然看到碎片在几个小时内达到 99%。 (该表目前大约有 130 万条记录。

我曾考虑用整数主键替换 GUID,但我不确定这是否可行;另外,由于我们的团队使用 GUID 作为主键而不是整数,所以我认为我没有足够的支持来做这件事。

我有什么办法可以让这个东西进行碎片整理?我使用的是 SQL Server Express,因此我无权访问 SQL 代理(因此无法定期运行维护计划来重建索引)。

我也很可能在将来的某个时候拆分这个数据库/表(因为数据量大),所以我可能需要 GUID 来合并表。

另外:我不能使用索引视图,因为我有一个内部选择,这对我来说很难展开。

【问题讨论】:

  • 也许应该将其移至 dba.SE 站点?

标签: sql-server-2008-express fragmentation


【解决方案1】:

我完全意识到我正在删除一个 8 年前(撰写本文时)开始的线程,但是对于 NEWID()、NEWSEQUENTIALID()、“不断增加的整数”存在一些严重的误解”,以及我简称为“ExpAnsive Updates”(带有“A”)的东西,它实际上是 ExpEnsive(带有“E ")。

让我们先讨论后者,这可能是 OP 遇到的真正问题......

只有很小的区别,当涉及到不必要的页面拆分创建和由此产生的碎片时,这并不重要,NEWSEQUENTIALID 和“不断增加的 INT”都以相同的方式工作......它们本身,它们只创建“好的”页面拆分(这也是“坏的”,但这是一个不同讨论的主题)。因此,参考最初发布的问题,Op 表示从完全随机的 NEWID 切换到“不断增加的”NEWSEQUENTIALID 似乎对正在创建的碎片量没有影响。

原因不是 NEWSEQUENTIALID 有问题(它没有)。碎片问题很可能是正在插入新行(这将导致 NEWSEQUENTIALID 没有碎片),然后这些新行会受到另一个进程来更新它们。如果更新是“ExpAnsive”,其中一行中的某些可变宽度列变得更宽,那么这将导致大量页面拆分。即使您使用相当低的 FILL FACTOR 构建索引也会发生这种情况,因为 INSERTS 不会因为它们达到 FILL FACTOR 而停止插入页面。相反,大量插入将插入到页面中,直到它们几乎 100% 填满(取决于每页的行数,这取决于插入的行的宽度),然后使用“好”页面创建一个新页面分裂几乎没有碎片,就像你使用一个不断增加的整数一样。

因此,您将所有这些行插入到连续的页面中,它们会被填充到尽可能接近 100% 的位置。一切都很好......没有碎片。但是随后您执行“插入后处理”来更新您刚刚插入的行。如果行的大小由于“ExpAnsive”而增加,那么 KAAAA-BOOOOOM !!!所有这些完全完整的页面最终都会分裂。

这种扩展的最常见来源之一是人们使用“穷人的审计”并且他们有一个从 NULL 变为某个值的“Modified_BY”列。有很多方法可以解决这个特定问题,但同样超出了本主题和帖子的范围。

转向由 NEWID() 生成的随机 GUID... 有很多不使用它们的理由,但是,与你一直相信的完全相反,碎片实际上不是其中之一。我已经以非常“爱丽丝的餐厅时尚”(大量图形和图形上的符号)的方式进行了几次演示,证明了这一点。为了制作适合这篇文章的超过 1 小时的演示文稿,我会告诉你,这一切都归结为人们不断犯的几个小但致命的错误......

  1. 他们继续使用 REORGANIZE,因为它被认为是“最佳实践”是主要问题。他们没有意识到 REORGANIZE 实际上并不能像他们认为的那样对 GUID 起作用。它实际上并没有在页面上提供额外的空间,而是删除了额外的空间,而且,我的索引管理员伙伴,实际上使 GUID 的碎片化。在随机指南上进行索引维护时,您不得使用重组!时期!!!即使您使用的是 Express 或 Standard Edition,也不会。如果您没有时间、资源或磁盘空间来重建它们,那么实际上最好不要对随机 GUID 进行任何索引维护,而不是使用 REORGANZE 来做错事。等到可以进行重建。

  2. 您必须在随机 GUID 键控索引上设置较低的 FILL FACTOR。将它们留在“0”几乎与重组它们一样糟糕。当然,取决于索引的行有多宽,每天插入多少行,以及您希望在随机 GUID 上使用绝对零页面拆分(甚至不应该是“好”的!!!)多长时间索引,我告诉人们将他们的 FILL FACTOR 设置为 71、81 或 91。我让所有这些都以“1”结尾的原因是因为当“ExpAnsive”更新时,你需要为随机 GUID 修复最后一件事不存在,这是下面的第 3 项。

  3. 您必须每天晚上检查基于随机 GUID 的索引。我选择给他们所有以“1”结尾的填充因子的原因是因为这就是你正在寻找的逻辑碎片的百分比。只要它们超过 1% 标记,您就必须重新构建它们,因为几乎整个索引中的每一页都处于将要拆分的位置。 (我称这些为“低阈值重建”)。现在,不要混淆。如果一切设置正确并且没有“ExpAnsive”更新,那么您的 GUID 键控聚集索引可以持续数周而没有分页或相关碎片,而您的更窄的非聚集索引可以在几个月内完全没有碎片!

当然,另一个大错误是“ExpAnsive”更新。这些将杀死几乎所有东西,但令人惊讶的是,随机 GUID 实际上比使用上述相同步骤的大多数其他东西更能经受住这样的冲击。

您真正需要做的是修复“ExpAnsive”更新,使它们不再是“ExpAnsive”。就像我说的那样,这是一个完整的主题,对于这篇文章来说是非常渴望的。

【讨论】:

  • 非常有趣!你能澄清一下第3条吗?假设我有 FILL FACTOR 81,我应该检查“每个该死的夜晚”什么? avg_fragmentation_in_percent
  • 第 3 项非常清楚,尽管我明白为什么人们可能不这么认为。它确实说要检查碎片,只要碎片达到 1%,就进行重建。您不得在 RANDOM GUID 上使用 REORGANIZE,因为它不会清理填充因子上方的区域。它必须是 1% 而不是更高的原因是因为随机 GUID 几乎是完美分布的。如果一个页面开始分片,几乎所有的页面也会开始分片。如果您等待更长的时间,您将遭受 MASSIVE 页面拆分和碎片的困扰。
  • 如果您想要超级详细的解释和演示,请参阅即将于 2021 年 7 月 28 日举行的 EightKB.Online 活动,我将在其中就该主题进行 1 小时的演示。我打破了 GUID 碎片化的神话,演示了如何使用 GUID 来实际防止碎片化,完全浪费了全世界错误使用的东西和索引维护的“最佳实践”。事实证明,这实际上是一种最糟糕的做法,并在我的演示文稿中的演示中证明了这一点。如果您在活动结束后才看到此内容,他们将进行录音。
【解决方案2】:

这是具有大量插入的 Guid 索引的预期行为。大多数情况下,您选择 guid 作为键,因为记录是由多个来源生成的,并且您需要使各个来源不会相互影响。这里的一个例子是离线移动设备。现场工作人员需要在未连接时创建新记录,因此移动设备可以安全地创建以 guid 为键的记录。稍后重新联机时,设备可以安全地与数据库同步,而不必担心任何密钥冲突。

如果您在单个服务器上生成 guid,通常最好使用简单的标识列。如果您真的想要这些指南,您仍然可以包含它们......您可能需要三思而后行将它们用于您的聚集索引。您可能希望在 guid 上进行集群的唯一原因是,如果稍后您将返回表并根据它的 guid 一次查询一条记录。您看到的插入率似乎不太可能。但是,如果是这种情况,您可以通过降低索引的填充因子来帮助缓解问题。这将增加使用的磁盘空间量(并意味着稍后会进行更多的磁盘寻道),但页面会更快填满,并且您将避免一些索引重新洗牌。

如果基于整数的键不存在问题,则在此处查看的另一个选项是顺序 guid。这仍然提供了独特性,同时也减少了碎片。

【讨论】:

  • 如果我对你的回答理解正确的话,可以概括为“让聚集的GUID索引成为非聚集索引”。这对吗?您提到的其他所有内容都是公认的背景。
【解决方案3】:

根据我自己的个人经验,将GUIDs 作为您的集群键可以对您的系统产生重大的积极影响——尤其是在索引碎片方面!

我的新 INT IDENTITY 聚类索引几乎没有任何碎片 - 即使经过数月的日常生产使用。绝对值得!!

在 SQL Server 中使用Guid 数据类型作为集群键是一个非常糟糕的选择 - 无论你怎么看...

请参阅 Kimberly Tripp(索引女王)关于该主题的一些博客文章:

以及她关于集群键主题的任何其他博客......

【讨论】:

  • 你能告诉我你每天看到多少插入物的粗略数量级(以量化你对“强烈”使用的评论)吗?
  • @ashes999:每天大约有 5-15K 的插入和更新。即使每天插入 25k-100k 次——int identity 上的聚集索引也会非常流畅——几乎没有任何碎片(
  • 你就是男人。加上一些索引视图,我的查询时间从 16 分钟缩短到了 30 秒。非常感谢:)
【解决方案4】:

看看this simple query in the stackexchange data explorer。看起来newsequentialid() 增加了 guid 的 最重要 部分,而不是最不重要的部分。这可能是您看到的碎片的一个原因。

如果您必须使用 guid,不妨考虑通过代码生成它们并在您的插入语句中发送它们,而不是依赖数据库生成它们。使用“comb”技术,将当前时间戳用作 guid 的一部分,以 最低有效位递增。


编辑

.. 或者如果您不想在代码中生成它们,您可以在数据库中执行类似的操作

CAST(CAST(NEWSEQUENTIALID() AS BINARY(10)) + CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER)

作为您的默认值,根据 this modification to the above query

【讨论】:

  • 我认为您的答案不正确。 newsequentialid 绝对比 newid 更不碎片化。创建自己的 GUID 是通过引入错误来打破非唯一性保证的好方法。但没有 DV :)
  • 我当然可以理解为什么人们可能会认为 NEWSEQUENTIALID GUID 的最重要部分可能就像引用的那段糟糕的代码一样,但答案是 GUID 的显示方式与显示方式完全不同他们被处理了。 GUID 是“字节洗牌”的,这会影响它们的排序方式。请搜索“GUID BYTE SHUFFLED”以获取更多信息以及 GUID 中字节位置的实际意义。
  • @ashes999 - 不正确。 NEWSEQUENTIALID 具有 IDENTITY 列的所有问题,包括插入后扩展更新导致的一些严重碎片,没有随机 GUID 的令人难以置信的优势(是的......我输入正确),并且它也有可能的安全错误,因为它包含发布计算机的 MAC 地址。我有一个演示,它清楚地演示了如何将 580 万行插入到随机 GUID 聚集索引中,碎片小于 1%,并且在 58 天内没有索引维护等等。只是在 SO 上的帖子上没有足够的空间。
  • @Reg - 至于 NEWSEQUENTIALID 不是连续的,它是。它只是没有按照人类习惯看到它的方式进行分类。如前所述,MSB 是最右边六个字节中最左边的字节。它对人类来说很有趣,但根据 UNIQUEIDENTIFIER 数据类型完美排序,它在幕后处理的方式是字节移位。您可以通过在使用 NEWSEQUENTIALID 的新表上插入 100K 行然后检查碎片来自己证明这一点......它实际上将是零碎片。
猜你喜欢
  • 2011-05-29
  • 1970-01-01
  • 2019-10-25
  • 2010-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多