重建非常大的主键索引


13

我有一个托管在Azure上的SQL数据库。问题在于大小已失控,我可以在主键聚集索引中看到多达99%的碎片。

我能够使用online=onoption 重建所有其他索引,并且不会影响性能。PK聚簇索引之一的大小大于200GB,并且为此REBUILD...WITH (ONLINE=ON)导致锁定。

实际上,确实有来自所有时区的用户都在访问该网站,因此,我找不到可以离线重建索引的时间。

在不造成站点停机的情况下重建大型索引的最佳策略是什么?

我相信重组将无济于事,因为碎片化率为99%。问题在于该表即使在线也被锁定。主要问题是索引大于200GB。主键是一个整数。


4
@Techy,即使具有很高的碎片,REORGANIZE也将减少叶子页的碎片和紧凑的空间,如REBUILD,效率较低。您确定大尺寸是由于碎片造成的吗?填充因子是多少?
Dan Guzman

您知道造成碎片的原因是什么吗?重建后多久您会回到1号广场?您可以发布有关您的桌子的更多信息吗?
令人惊叹的

2
@Techy我已经根据您的评论编辑了问题,以添加一些其他信息。如果您还在问题中包括表定义,以及与“即使在[重建]在线时表也被锁定”相关的其他详细信息,这将很有帮助。您看到什么样的等待?
AMtwo

Answers:


9

即使为时已晚,我仍要提出回应,希望它可以帮助或至少拒绝对此问题的其他一些想法/评论,因为我认为这是一个好问题。

首先,我不知道您是否正在执行此操作,但是请不要假设索引上的高碎片级别总是会导致性能下降。过时的统计信息(例如sys.dm_db_stats_properties)和大量的每页空白(即sys.dm_db_index_physical_stats dmv中的avg_page_space_used_in_percent列)在性能问题上的相关性比单独使用碎片更重要。是的,高度碎片化的索引将产生更多的预读,并且您通常会看到过时的统计信息和每页更高级别的空白以及碎片,但是碎片与查询计划的优化没有直接关系,也与磁盘加载索引没有多少内存直接相关实际上会消耗掉。 查询计划会受到统计数据的影响,并且您的内存占用量会膨胀,留有更多空白。例如,索引的碎片率为99%,但平均平均值小于5%。与过时的统计信息或过大而无法完全容纳在内存中的索引的恒定分页导致的执行计划不佳相比,空白和最新的统计信息可能不会引起严重的性能问题每页存在的空白数。

如果碎片问题确实是一个问题,可以ALTER INDEX ... REORGANIZE通过在注释中发表由Dan Guzman标识的声明来在线减少碎片。这不会像REBUILD操作那样简化索引的创建,但是会减少碎片。此处的关键是确定数据库上使用率较低的窗口,然后运行它。这可能是15分钟或几个小时,显然时间越长越好,但是这里的关键是此操作不会回滚,并且即使您在执行过程中将其杀死也不会保留任何进度。

如果在消除碎片的理想环境中,在此表上使用分区是否更有意义? Azure SQL数据库确实允许表分区,并且Microsoft有一篇很棒的文章概述了Azure SQL数据库的一些分区策略。如果您的数据是非挥发性的,则对其进行分区可以帮助减少维护需求,如果与表压缩结合使用,您甚至还可以减少整体存储空间。Alberto Murillo的较早答案暗示了使用水平分区 基于数据区域,这种方法可能有助于为您创建一些维护窗口,因为您的数据将更加针对特定区域而不是全局。

当前没有维护窗口,要过渡到分区表并不容易,但是您可以使用Maria Zakourdaev概述的方法,该方法在当前表的顶部使用分区视图,并使用新的分区表开始分区。未来的数据。随着时间的流逝(并希望清除旧数据),您最终可以完全过渡到分区表。同样,我不知道您的数据或应用程序,但是也许可以使用这种方法。


4

首先,考虑碎片是否重要很重要。

如果您的查询仅执行单行查找,则可能根本不会注意到碎片。在现代SAN上,SAN级别的缓存可能使物理IO足够快,以至于碎片无关紧要。在SSD上,由扫描碎片索引引起的随机IO模式实际上可能比未碎片化的数据具有更好的性能。

通常,人们会注意到重新建立索引可以解决性能问题。重建索引也会建立新的统计数据。实际修复可能是最新统计信息,而不是重建索引。UPDATE STATISTICS...WITH FULLSCAN可能是解决同一性能问题的一种更便宜,更快,更不麻烦的方式。

如果您没有因碎片造成的问题,那么您可能会花费大量的时间和精力,而没有实际的收获。

其次,有两种碎片:

  1. 物理碎片。这就是大多数人想到碎片时的想法。页面混乱,需要重新排序。当扫描一个索引这种类型的碎片有时可以是一个问题。我通常注意到这对物理读取的性能影响最大。如果您正在查看的结果sys.dm_db_index_physical_stats,则此数字为avg_fragmentation_in_percent列。

  2. 低密度碎片化。这种碎片是由仅部分填充数据的页面引起的。您的数据密度较低,因为您的数据分布在不必要的页面上。结果,读取数据需要更多的IO,因为数据分布在不必要的页面上。这会影响逻辑和物理读取。如果您正在查看的结果sys.dm_db_index_physical_stats,则此数字为avg_page_space_used_in_percent列。仅在使用SAMPLEDDETAILED模式时填充此列。

那么您如何处理:

物理碎片:如果您只是为了追求高数字avg_fragmentation_in_percent,请真正考虑您是否在浪费时间。确保您的实际查询效果不佳,并使用测试环境通过消除碎片来确认您要解决的问题。

您可以通过这样做解决物理碎片ALTER INDEX...REORGANIZE。该REORGANIZE操作是在线的,一次移动一页以将其重新组织为物理顺序。如果您在REORGANIZE中途取消一条语句,则将保留所有已执行的工作-仅回滚当前正在移动的一页。在REORGANIZE高度分散的大型表上执行操作可能需要更多的总事务日志空间,并且在完全恢复模式下可能会产生大量的事务日志备份。REORGANIZE高度分散的索引所花的时间可能比REBUILD它要长。

您经常会看到建议REBUILD在高碎片索引上执行,而不是在REORGANIZE-上执行,这是因为从头开始重建可能会更有效率。但是,重组可能是“更在线的”操作,有时甚至对于高度分散的索引也是首选的。

低密度碎片无法通过固定REORGANIZE。它只能通过执行来固定ALTER INDEX...REBUILD。通过使用做索引ONLINE=ON,您应该能够最大程度地减少阻塞。但是,REBUILD仍然需要花一点时间将旧索引替换为新索引。在非常繁忙的系统上,获得此独占锁定有时可能是一个问题。通过使用诸如sp_whoisactive之类的东西在重建期间检查阻塞,并查看锁和等待的详细信息,您应该能够确认是否遇到此问题。WAIT_AT_LOW_PRIORITY如果您知道即将出现利用率低的时期,并且在活动量下降到足以获得该锁定的程度时,重新构建可以“潜入”此交换,则使用该选项可能很有用。注意长时间运行REBUILD操作也将是长期运行的公开交易。长时间运行的开放式事务可能会遇到与事务日志使用/重用相关的问题。如果使用镜像或可用性组,则还需要考虑辅助副本上的事务日志重做。


低密度碎片(也称为“内部碎片”)通常由固定REORGANIZE。从BOL:“重新组织还会压缩索引页。” 好吧,只要索引的当前FILLFACTOR允许您追求的密度即可。
Granger

2

注意

在此评论之后:

您将丢失在复制过程中插入的行。如果要通过锁定表来防止这种情况,那么最终会遇到与他的问题中所述的OP相同的问题。另外200Gb也不会免费提供:-) – Marco Marco 17 年9月5日在11:18

...我知道这种方法行不通。

我将把这个答案留作做事的例子。


如果您的Azure DB上有200+ GB的可用空闲空间,则可以通过将数据复制到一个全新的表中并在其中进行排序来偷偷摸摸地进行“重建”。

尝试:

  • 脚本LiveTable化为空NewTable
  • 复制LiveTableNewTable
  • 重命名LiveTableOldTable
  • 重命名NewTableLiveTable

显然,用你的表的名称,而不是LiveTable


奥利奥,我将使用与您相同的方法。即使在复制过程中插入了行,您仍然可以在将NewTable重命名为LiveTable之后添加它们。您在这里避免的主要问题是延长的停机时间。您甚至可以对其进行bcp(I / O复制到)。这不是一个坏主意,所以我也不明白:
Koen D

1

理想情况下,如果索引设计合理,我们就不需要摆弄锁定机制。

在我看来,您需要接受锁定才能对聚集索引进行碎片整理。如果很有可能再次发生这种情况,那么可以考虑重新设计聚簇索引(它应该是狭窄的,唯一的,静态的并且不断增长的)。

我不确定您使用的是哪个版本的SQL Server,但是您可以在2012年尝试以下方法:

  • SET DEADLOCK_PRIORITY LOW -这告诉引擎,当/如果发生索引重建,索引重建应该是死锁的牺牲品。

  • MaxDOP = 1 -MaxDOP值限制了并行用于创建索引的逻辑CPU的总数(2005向上-仅企业版)。

您也可以更改页面/行锁配置,但是如果没有测试,我不会这么做。您可能会使锁定变得更糟,尤其是在索引设计不良的情况下。

从2014年开始,以下选项基本上告诉引擎允许其他会话继续进行,并且在线索引操作等待:

(WAIT_AT_LOW_PRIORITY (MAX_DURATION = 1 MINUTES, ABORT_AFTER_WAIT = SELF))

0

我使用了与上述奥利奥相同的方法,并取得了巨大的成功!唯一缺少的是复制数据并进行最后一次重命名后,需要运行更新脚本。

更新将如下所示:

Insert from OldTable into LiveTable
  Where not exists (select From OldTable Where LiveTable.Key = OldTable.Key)

如果“密钥”是“身份”列,则需要使用稍微不同的方法。


正如奥利奥(Oreo)的回答所述,如果仍然有数据添加到原始表中,那么除非您锁定了原始表,否则该方法将行不通,这不符合锻炼的目的
汤姆V-尝试topanswers.xyz

-2

尝试使用分片在地理上分布数据库的数据。然后,您将能够为每个地理位置标识不同的维护窗口,并且进行维护的时间将缩短。这也将提高性能。您可以了解更多关于文章。不要等待数据库变大。

在大型数据库和用户24 x 7连接的情况下,您需要使用索引重组和仅更新需要更新的统计信息(sp_updatestats),以最大程度地减少维护所需的时间以及对用户的影响。

希望这可以帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.