在聚集索引上重建,为什么数据大小会缩小?

Dan*_*örk 10 sql-server clustered-index

当我们对一个表的聚集索引进行重建时,该表中有大约 15GB 的数据并且数据大小缩小到 5GB,这怎么可能?删除了什么样的“数据”?

数据大小我指的是 DBCC sp_spaceused 的“数据”列

在聚集索引上重建之前:

name                  rows        reserved    data        index_size  unused
LEDGERJOURNALTRANS    43583730    39169656 KB 15857960 KB 22916496 KB 395200 KB
Run Code Online (Sandbox Code Playgroud)

在聚集索引上重建后:

name                  rows        reserved    data        index_size  unused
LEDGERJOURNALTRANS    43583730    29076736 KB 5867048 KB  22880144 KB 329544 KB
Run Code Online (Sandbox Code Playgroud)

用于重建的 TSQL:

USE [DAX5TEST]
GO
ALTER INDEX [I_212RECID] ON [dbo].[LEDGERJOURNALTRANS] REBUILD PARTITION = ALL WITH ( PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, ONLINE = ON, SORT_IN_TEMPDB = OFF, DATA_COMPRESSION = PAGE, FILLFACTOR = 85 )
GO
Run Code Online (Sandbox Code Playgroud)

Dav*_*ett 16

当表有聚集索引时,索引就是表数据(否则你有一个堆类型的表)。聚集索引的重建(实际上是任何索引,但对于非聚集索引,空间不会被算作“数据”)将导致部分使用的页面被合并为更完整的形式。

当您按照索引顺序将数据插入索引(集群或其他方式)时,将根据需要创建叶页,并且您将只有一个部分页面:最后的页面。当您不按索引顺序输入数据时,需要拆分页面以使数据适合正确的位置:您最终会得到两页,它们大约占了一半,新行进入其中之一。随着时间的推移,这可能会发生很多,消耗相当多的额外空间,但在某种程度上,未来的插入将填补一些空白。非叶页也会看到类似的效果,但实际数据页的大小要比它们大得多。

删除也可能导致部分页面。如果您删除页面中的所有行,它会被计为“未使用”,但如果它还有一行或多行数据,它仍会被计为正在使用中。即使一页中只有一行使用 10 个字节,该页在使用的空间计数中也计为 8192 个字节。未来的插入可能会再次填补一些空白。

对于可变长度的行,更新也可以产生相同的效果:随着行变小,它可能会在其页面中留下以后不容易重用的空间,如果几乎整个页面中的一行变长,它可能会强制页面拆分.

SQL Server 不会花时间通过重新排列页面的使用方式来尝试规范化数据,直到明确告知您的索引重建顺序,因为此类垃圾收集练习可能是性能噩梦。

我怀疑这就是您所看到的,尽管我会说为数据绝对需要的 2.7 倍分配足够的空间是一个特别糟糕的情况。这可能意味着您有一些随机的东西作为索引中的重要键之一(可能是 UUID 列),这意味着新行不太可能按索引顺序添加,和/或最近发生了大量删除。

分页示例

按索引顺序插入固定长度的行,其中四个适合一个页面:

Start with one empty page: 
        [__|__|__|__]
Add the first item in index order:
        [00|__|__|__]
Add the next three
        [00|02|04|06]
Adding the next will result in a new page:
        [00|02|04|06] [08|__|__|__]
And so on...
        [00|02|04|06] [08|10|12|14] [16|18|__|__]
Run Code Online (Sandbox Code Playgroud)

现在要添加不符合索引顺序的行(这就是为什么我只在上面使用偶数):添加11意味着要么扩展第二页(不可能,因为它们的大小是固定的),将 11 以上的所有内容向上移动一个(太贵了大索引)或像这样拆分页面:

[00|02|04|06] [08|10|11|__] [12|14|__|__] [16|18|__|__]
Run Code Online (Sandbox Code Playgroud)

从这里开始,添加1317不会导致拆分,因为相关页面中目前有空间:

[00|02|04|06] [08|10|11|__] [12|13|14|__] [16|17|18|__]
Run Code Online (Sandbox Code Playgroud)

但添加 03 将:

[00|02|03|__] [04|06|__|__] [08|10|11|__] [12|13|14|__] [16|17|18|__]
Run Code Online (Sandbox Code Playgroud)

如您所见,在这些插入操作之后,我们目前分配了 5 个数据页,总共可以容纳 20 行,但那里只有 14 行(“浪费”了 30% 的空间)。

使用默认选项的重建(见下文关于“填充因子”)将导致:

[00|02|03|04] [06|08|10|11] [12|13|14|16] [17|18|__|__]
Run Code Online (Sandbox Code Playgroud)

在这个简单的例子中保存一页。很容易看出删除与乱序插入的效果相似。

减轻

如果您希望数据相对于索引顺序以相当随机的顺序出现,您可以FILLFACTOR在创建或重建索引时使用该选项来告诉 SQL Server 人为地留下空白以供以后填充 - 从长远来看减少页面拆分但最初占用更多空间。当然,弄错这个值可能会使事情变得更糟,而不是让情况变得更好,所以要小心处理。

页面拆分,尤其是在聚集索引上,可能会对插入/更新产生性能影响,因此FILLFACTOR有时会因为这个原因进行调整,而不是看到大量写入活动的数据库中的空间使用问题(但对于大多数应用程序,读取大于写入通过几个数量级,您通常最好将填充因子保留为 100%,除非特定情况下,例如您在具有有效随机内容的列上建立索引)。

如果您也需要这种级别的控制,我认为其他大牌数据库也有类似的选择。

更新

关于ALTER INDEX在我开始键入上述内容后添加到问题中的语句:我假设这些选项与第一次构建(或上次重建)索引时的选项相同,但如果不是,那么如果添加了这个压缩选项,它可能会非常重要时间。同样在该语句中,fillfactor 设置为 85% 而不是 100%,因此在重建后每个叶页面将立即为大约 15% 的空。

  • +1 如果页面填充因子小于 100%,例如如果页面填充因子为 50%,则新重建的聚集索引(*table*)将是使用 100% 填充因子重建的两倍大. (2认同)

Aar*_*and 6

当您重建索引时,它实际上将所有数据放置到新页面上。我怀疑发生的情况是您在重建之前删除了大量数据,例如删除一列、更新可变宽度列以减少数据、更改固定宽度列大小或删除大量行。这些操作中的任何一个都可能在页面上留下大量空白空间,在重建之前不会被回收。中的“数据”列sp_spaceused不是测量实际数据,而是用于存储数据的8K页数。由于重建,这些页面现在更满,因此相同数量的数据适合较少数量的页面。


RLF*_*RLF 5

sp_spaceused存储过程是不检查在数据库中的行的总culmulative大小。它在为数据分配的盘区的累积大小中报告为保存该数据而分配的空间大小。

如果有大量空闲空间可用,例如来自许多已删除的行,那么出于性能原因,聚集索引的重建将压缩页和区中的空间以提高效率(即更小)。

因此,不应丢弃任何数据,但重建过程使嵌入数据页中的可用空间再次可用。