每日25k,在群集GUID索引上插入99%的碎片

时间:2012-01-17 13:56:31

标签: sql-server-2008-express fragmentation

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

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

我曾考虑用整数主键替换GUID,但我不确定这是否有效;此外,由于我们的团队使用GUID作为主键而不是整数,我认为我没有足够的支持来做到这一点。

我可以选择将此内容进行碎片整理吗?我使用的是SQL Server Express,因此我无法访问SQL Agent(因此无法定期运行维护计划来重建索引)。

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

另外:我不能使用索引视图,因为我有一个内部选择,这对我来说很难放松到连接中。

4 个答案:

答案 0 :(得分:5)

根据我个人的经验,抛弃GUID作为您的群集密钥可以对您的系统产生重大的积极影响 - 特别是在索引碎片方面!

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

在SQL Server中使用Guid数据类型作为群集密钥是非常糟糕的选择 - 无论您采用哪种方式...

请参阅Kimberly Tripp的一些(索引女王)博客文章:

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

答案 1 :(得分:1)

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

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

如果基于整数的键是不可能的,那么在这里查看的另一个选项是顺序guid。这仍然提供了独特性,同时也减少了碎片。

答案 2 :(得分:1)

我完全意识到我正在死于8年前启动的线程(在撰写本文时),但是对于NEWID(),NEWSEQUENTIALID()和“ Ever-inreasing-INTs”存在一些严重的误解”,而我简称为“ Exp A nsive更新”(带有“ A”)的东西,实际上是Exp E nsive(带有“ E” “)。

先介绍后者,这可能是OP面临的真正问题...

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

其原因不是NEWSEQUENTIALID有问题(不是)。碎片问题很可能是要插入新行(这将导致使用NEWSEQUENTIALID不会产生碎片),然后使这些新行遭受另一个更新它们的过程。如果更新为“ Exp A nsive”,其中某行中的某些可变宽度列变得更宽,则将导致大量页面拆分。即使使用较低的填充因子来建立索引,也会发生这种情况,因为插入并不会因为其达到填充因子而停止插入页面。取而代之的是,大量插入将插入到页面中,直到它们将近100%充满为止(取决于每页的行数,这取决于要插入的行的宽度),然后使用“好”页面创建新页面几乎肯定没有碎片,就像您使用的是不断增加的整数一样。

因此,您将所有这些行插入连续的页面中,并且它们被填充到尽可能接近的100%。一切都很好...没有碎片。但是,然后执行“插入后处理”,以更新刚插入的行。如果由于“ Exp A nsive”而导致行的大小增加,则说明KAAAA-BOOOOOM!所有那些完全整齐的页面最终都分裂了。

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

将齿轮转换为由NEWID()生成的随机GUID ...有很多理由不使用它们,但是与您已经相信的完全相反,碎片实际上不是其中之一。我已经用非常“爱丽丝的餐厅时尚”作了几个演示(证明了大量图形和图形符号)。为了使一个多于一个小时的演示适合本文,我将告诉您,这都归结为人们不断犯下的几个小但致命的错误...

  1. 他们一直在使用REORGANIZE,因为据认为这是“最佳实践”是主要问题。他们没有意识到REORGANIZE实际上并不以他们认为可能的方式在GUID上运行。实际上,它没有在页面上提供额外的空间,而是删除了额外的空间,并且,那是我的索引争吵者,实际上使GUID的碎片化了。在随机向导上进行索引维护时,请勿使用重组!期!!!即使您使用的是Express Edition或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键聚簇索引可以连续数周没有页面拆分或相关的碎片,而更窄的非聚簇索引可以在MONTHS上完全没有碎片!

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

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

答案 3 :(得分:-1)

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

如果你必须使用guid,或许可以考虑通过代码生成它们并在insert语句中发送它们,而不是依赖于生成它们的数据库。使用“梳子”技术,将当前时间戳用作guid的一部分,以最低有效数字递增。


修改

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

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

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