当一个表具有聚集索引时,该索引就是表数据(否则您具有堆类型表)。重建聚集索引(实际上是任何索引,但对于非聚集索引而言,该空间不会被视为“数据”)将导致部分使用的页面合并为更完整的形式。
当您按照索引顺序将数据插入索引(集群或其他)时,将根据需要创建叶页,并且您将永远只有一个分页:最后一个分页。当您输入的数据不按索引顺序排列时,需要对页面进行拆分以使数据适合在正确的位置:您最终得到了两个页面,这些页面大约一半已满,新行进入其中一个页面。随着时间的流逝,这可能会发生很多,消耗大量的额外空间,尽管将来的插入将在某种程度上填补一些空白。非叶子页面也会看到类似的效果,但是实际数据页面的大小远比它们重要。
此外,删除可能会导致页面分页。如果删除页面中的所有行,则将其视为“未使用”,但如果剩余一行或多行数据,仍将被视为正在使用。即使一页中只有一行使用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|__|__]
现在要以不按索引顺序添加行(这就是为什么我仅在上面使用偶数的原因):添加11
将意味着要么扩展第二页(不可能,因为它们的大小是固定的),就将11以上的所有内容都向上移动一个(太昂贵了)大索引)或按以下方式拆分页面:
[00|02|04|06] [08|10|11|__] [12|14|__|__] [16|18|__|__]
从这里开始,添加13
和17
不会导致拆分,因为相关页面中当前有空间:
[00|02|04|06] [08|10|11|__] [12|13|14|__] [16|17|18|__]
但是添加03将:
[00|02|03|__] [04|06|__|__] [08|10|11|__] [12|13|14|__] [16|17|18|__]
如您所见,在执行这些插入操作之后,我们目前已分配了5个数据页,总共可以容纳20行,但是那里只有14行(“浪费”了30%的空间)。
使用默认选项进行重建(请参阅下面的“填充因子”)将导致:
[00|02|03|04] [06|08|10|11] [12|13|14|16] [17|18|__|__]
在这个简单的示例中保存一页。可以很容易地看出删除操作如何能与乱序插入产生类似的效果。
减轻
如果期望数据相对于索引顺序而言是相当随机的顺序,则FILLFACTOR
可以在创建或重建索引时使用该选项,以告诉SQL Server人为地留出空白以供以后填充-从长远来看减少页面拆分,但是最初占用更多空间。当然,弄错了这个值可能会使事情变得更糟,而不是使情况变得更好,因此请谨慎处理。
页面拆分(特别是在聚集索引上)可能会对插入/更新产生性能影响,因此FILLFACTOR
有时会对此进行调整,而不是进行大量写入活动的数据库中的空间使用问题(但对于大多数应用而言,读取胜于写入)在几个数量级上,通常最好将填充因子保持在100%,除非是特殊情况(例如您对具有有效随机内容的列进行索引)。
我假设其他大型数据库也有类似的选择,如果您也需要这种级别的控制。
更新资料
关于ALTER INDEX
在我开始键入以上内容后添加到问题的语句:我假设选项与首次构建(或最后重建)索引时相同,但如果不相同,则如果添加了压缩选项,则压缩选项可能非常重要时间。同样在该语句中,填充因子设置为85%而不是100%,因此,每个叶子页在重建后将立即变为约15%的空白。