[摘]确定SQL Server填充因子(FILLFACTOR)值的方法
设置填充因子是否重要?我认为是。很多资料都提到,缺省的填充因子取值一般情况下是较优的,而且建议除非你很清楚在做什么,否则不要更改填充因子取值。就像数据库管理员常识那样,除非你理解你正在做什么,否则不更改任何设置是明智的。说得虽然没错,但是到底该如何确定填充因子取值,以及知道该做什么?我试图找到一个确定取值的简单公式。你猜结果如何?我找不到。
为了理解填充因子,有必要好好理解聚集和非聚集索引,堆表(heap tables),扩展盘区(extents),页(pages),页拆分(page splits),以及DBCC SHOWCONTIG命令结果。如果你不熟悉这些概念,我建议参考SQL Server联机帮助,或者相关背景的资料。理解SQL Server物理文件结构的组成是十分重要的。
基本上填充因子是指当索引被创建和重建时,指定存储记录的叶节点页空间的百分比。我们也许以前都看过这个定义,但是该定义中有一个常被人忽视的关键点--“当索引被创建和重建时”。一旦页拆分发生,经过拆分后生成的两个新页上,原来设置的填充因子取值就失效了。当索引创建时,如果指定填充因子为80,意味着每个页只使用80%的空间。在某个页填入数据后,例如添加新记录或者修改记录,页拆分为两个新页,并且填充空间均为50%。原来那个填充因子对这两个新页失效。如果你没有重建索引,那么除非数据量很小,或者数据更新很小,否则很可能会产生很多页拆分,并且最早的填充因子会失效。
还是那个问题,我们该设置填充因子为多少呢?取值低,则需要更多的页来存储数据,因而读取范围大;这会影响性能。取值高,则会造成大量的页拆分;同样会影响性能。页拆分同时消耗CPU和I/O资源,但页拆分本身确实是必要的。也许你知道在只读表上应该设置填充因子高,而有大量更新的表上应该设置较低。这个结论没错,但是关键是你怎么知道表上面数据的更新程度?正如我现在的数据库,要指出哪些表是更新较低的,而那些是较高的,是一件很花时间的任务。其中的一个数据库有22,000张表,所以你能想象,要做完上述的工作有多漫长了。并且即使同一张表上的不同索引也具有不同的碎片率。所以要定义“高”和“低”的设置,是一件令人绝望的事情。
我想在这里阐述的东西不是严格的科学,也不是放置四海皆准的方法,而是一般情况下,我经常遵循的常识性的东西,就象当创建物理模型时,经常在外键上创建索引的常规做法一样道理。因此我想我已经讲明白本文的性质了。请在一两个表上试用这些方法,看看到底效果如何。
目标
- 在索引大小和页拆分之间找到平衡点。当添加影响索引的数据时,较小的填充因子减少页拆分;较大的填充因子正好相反。
- 尽量减少扩展盘区交换次数,并保持页数据的连续。每个索引的扫描密度(Scan Density)指标越大越好。我就比较喜欢将扫描密度保持在90%或者更高的水平,虽然在索引重建时,并不总是能达到。
- 防止在索引重建后,每秒页拆分(page splits/sec)指标突然升高。这会导致我的服务器崩溃。
- 尽量使得每个索引的平均页密度(Average Page Density)指标越高越好。该指标越高,说明读取操作越少。
- 平均页密度(Average Page Density)接近或者高于填充因子取值。在完美的情况下,当索引重建时,Average Page Density取值应该在填充因子取值到100%之间。
- 保持数据库平稳持续增长。你可以监控索引重建前和重建后数据库磁盘容量的大小变化。如果重建后,占用空间减少了,是好现象。
- 在索引重建时,尽量保持性能参数平稳。如果只能有一个目标,我认为这就是我要选择的目标。上面的所有目标最终是为该目标服务的。我喜欢在整个过程中性能保持平稳和一致。如此,可以减少很多性能问题的担忧。
要紧的事先做,让我们开始吧
首先定义一个重建索引的调度作业。我会根据数据库的不同创建不同的调度。例如对于相对较小的数据库,我创建的调度作业为:在非高峰时间,对所有用户表执行DBCC DBREINDEX。在某些阶段,有些数据库会受到额外的关注。这对于如何设置填充因子是很重要的一个因素。填充因子的设置依赖于索引重建的频率。
下面将讲述一些调试数据库的规律性东西。你可以列举很多这样的规律,不过我想分享一些我认为更关键的规律。首先,性能监控时请关注Page Splits/Sec这个指标。记住页拆分是一项不得不做的工作,同时也因此而带来性能问题。其次,获取DBCC SHOWCONTIG的信息。我有一个调度作业,该作业每个星期执行一次DBCC SHOWCONTIG,负责获取我所有服务器上的用户数据库结果,并放到某个表中。当然我会在这些信息上添加实例和数据库名字的字段以方便查询分析。运行DBCC SHOWCONTIG,并加上ALL_INDEXES和TABLERESULTS的选项。TABLERESULTS选项能让我们方便地读取和操作结果数据。ALL_INDEXES选项是必要的,因为如果没有该选项,你看的的结果是针对聚集索引和堆的。我们需要掌握全局并分别对待每个索引。这是因为某个表上的某个索引的好坏,并不意味该表上别的索引也有相同结论。一个索引也许建立在相对变化大的字段上,而另一个也许建立在相对静态的字段上。因此你还应该获取一系列的表名,索引,以及sysindexes表中的OrigFillFactor数值.
Ok,让我们按照这些准则并将其应用在数据库上。由于DBCC SHOWCONTIG需要消耗资源,所以最好在系统不那么繁忙的时间做该工作。也许你的数据库有许多表和索引,所以最好能够按字母顺序或者按行数来排列得到的结果。起初,看那些结果会比较麻烦,不过只要用一段时间,你就会慢慢体会到那些数值的重要性了。
在每次调度中执行DBREINDEX命令。总是要留意的是,该命令的执行会十分消耗资源,在某些情况下有可能让数据库崩溃,而且我直觉地认为会降低数据库性能。比如当填充因子设置为100,并且数据库同时有大量的insert操作的时候。此时会发生什么?首先,数据库会有大量的页拆分。因为填充因子取值设置不当,重建索引工资也许会在好几天内都对性能产生影响。
在重建索引后,监视Page Splits/Sec指标,看看是否上升了。从重建索引开始,直到下一次重建前,之间间隔要超过24小时。在这段时间内需要获取三次Page Splits/Sec的取值,一次是在索引重建完成时,一次是距离下次重建的中间时间点,还有一次就是下次重建前一刻。
在下次重建前的24小时内,运行DBCC SHOWCONTIG,并保存结果信息。
利用最初的填充因子取值,和由DBCC SHOWCONTIG命令获得的信息,就可以开始进行新的填充因子设置了。通过重建索引的调度作业,在上述时间段内观察索引的碎片程度,当然,前提是事务造成的容量变化不大。
我并不会纠缠较小的索引。所谓较小的索引,不仅指索引对应的记录数小,而且指涉及的页数小。这样可以减少很多分析的时间,而且填充因子的修改也不至于影响太大。关于每个索引,可以通过Scan Density指标来观测。通常,这个指标是关键,不过也有例外。如果Page Density指标为24%,这就意味着索引正被拆分了,一些数据发生改变了。当平均Page Density为50%,而原始的填充因子取值为90,那么可能有问题了。既然我知道下次索引会在什么时候被重建,所以我还会看看,当原始的填充因子为80,并且平均Page Denisty为我要增加的填充因子取值的90%时的情形。
填充因子设置的一般性准则和指导
- 索引重建任务的时间间隔要相对一致。
- 如果索引较小,就没有必要去调整填充因子。
- 在索引级别上进行监控和更新,而不是表级别上。
- 保存填充一直在0,或者75和100之间。如果你要将填充因子设置为低于75,那么你必须自信你在做什么。保持较低的Scan Density和较低的平均Page Density是十分重要的情形。做一些观察,在将填充因子取值降低前,找出表被读取的频繁程度。
- 如果Scan Density高于或等于90%,别去改变填充因子,或者调整任务中填充因子至少不应该是首先被调整的。
- 如果Scan Density在60%到90%之间,小小地降低一下填充因子,例如降低幅度2%。
- 如果Scan Density低于60%,则加大降低填充因子的幅度。填充因子的取值我一般会取平均Page Density和最初的填充因子之间的中值。例如Original Fill factor = 100,Average Page Density = 60,那么我就取填充因子为80。
- 如果Average Page Density大于Original Fill Factor,并且Scan Density接近于100%,那么要提高填充因子取值。这样做很好,因为你可以把更多的数据放在同一个页中。例如:Fill Factor = 80,Scan Density = 98,Average Page Density = 88。此时,在下次索引重建之前,页面按照8%的速度填充,而且该速度相对持续稳定;这种情况吓,就可以提高填充因子取值,但是不要超过92。因为按照8%的增长速度,页面马上会填充到100%而引起页拆分。我建议是提高到88%,并且开始观察下一步数据变化如何。
- 要力图避免改变填充因子过快。我建议多观察一些周期,然后微调。
这就是我提供的设置填充因子的常规原则和步骤。如果你索引重建周期很规律,你就可以发现索引碎片如何。通过使用DBCC SHOWCONTIG获取的数据,可以在下次索引重建前,辅助你来决定填充因子的新取值。通过监视指标,可以慢慢学到页拆分和填充因子设置的技巧。我曾经尝试得出设置填充因子的公式,这花了我很多时间。上面列举的规则很直观,一般情况下我会将填充因子尽可能的设置高一些。在很多情况下,这是基本的准则。
看完上面的规则后,你会觉得这并不难。因此快点应用到实际上,但是不要期望结果是十全十美的,因为这和目标有关。
其它的观点和考虑
- 使用时间长的数据库的填充因子一般来说比新数据库要高。这很容易解释。很多表增长是一个常数,既不是线性也不是指数型。假设某个索引最初需要1000页,索引重建期间扩展了500页,填充因子值位90。这相当于空余0.10*8096=790KB的容量用于后面数据的增加或更新。此时的填充因子被认为也许高了一些,应该调低。但是一旦填充因子同样为90,但是如果那个表最初有10,000页,那么意味着有大约7906KB的空闲空间可以用于后面的数据增加或更新。因此10,000页的时候,提高填充因子取值是有利的。当设置填充因子的时候,索引的增长和大小是必须考虑的。
- 和较小的表/索引相比,具有低扫描密度或者大幅度下降的平均页密度的大表/索引更应该被关注。它们具有更多的页拆分,这些页拆分会给较大的索引带来大幅度的下降。因为我们的目标是减少页拆分!一个十分大的索引下降2%的扫描密度,从纯量上看,远远大于较小的索引降低30%的的幅度。
- 密切关注通过SHOWCONTIG得到的扫描密度和平均页密度指标。从这两个指标,可以得到很多相关的信息。
- sysindexes表中的初始填充因子取值并不代表索引创建时的填充因子取值,至少该取值并非是必要的。该数值只在最后一次创建或重建时有用。
- 你可以用同样的方法来考查堆表的情况。
- 当你觉得填充因子设置合适后,请在一段时间后再观察一下。这需要不断的维护。