FILLFACTOR – Vor- und Nachteile

Die Option FILLFACTOR ermöglicht bei der Erstellung von Indexen einen Füllgrad in Prozent zu bestimmen, mit dem Microsoft SQL Server die Indexseiten mit bestehenden Daten auffüllen soll. Bei einem Kunden wurde diese Option ebenfalls verwendet, jedoch wurden für ALLE Indexe ein serverweiter FILLFACTOR von 80% konfiguriert! Diese Strategie führte leider in in manchen Bereichen der Anwendung eher zu Nach- als zu Vorteilen. Interessant bei diesem Sachverhalt war jedoch die Aussage des IT-Leiters, dass ihnen die Einstellung eines pauschalen Wertes empfohlen wurde, um für alle Indexe kostspielige Page Splits zu vermeiden.

Warum FILLFACTOR

Grundsätzlich ist der FILLFACTOR tatsächlich ein sehr gutes Korrektiv für OLTP-Systeme, um kostspielige Page Splits zu vermeiden. Ein Page Split wird immer dann ausgeführt, wenn eine Indexseite / Datenseite nicht mehr in der Lage ist, die Datenmenge, die bei einem INSERT oder UPDATE generiert wird, aufzunehmen. Der Nachteil eines Pagesplits liegt in der logischen Ordnung dieser neuen Datenseiten. Die nachfolgende Abbildung soll das Problem verdeutlichen.

Wenn ein Index neu aufgebaut wird, werden die Daten so in die Datenseiten eingeordnet, dass die Datenseiten logisch hintereinander gelesen werden können. In der Abbildung ist der Index zunächst ideal aufgebaut und die Daten können hintereinander gelesen werden. Anschließend soll auf der dritten Datenseite ein neuer Datensatz “RAC” eingepflegt werden. Da dieser Datensatz jedoch nicht mehr auf die Datenseite passt, muss die Datenseite geteilt werden. Hierbei werden ca. 50% der Daten in eine neue Datenseite verschoben um Platz zu schaffen. Anschließend wird der neue Datensatz – für den nun Platz vorhanden ist – eingetragen. Auf Grund dieser Trennung kann Microsoft SQL Server nun aber die Daten nicht mehr hintereinander lesen sondern muss zwischen den Datenseiten springen. Bei einem Index Scan ändert sich die Lesefolge von 1, 2, 3, 4, 5 in 1, 2, 3, 6, 4, 5. Gleichzeitig tritt noch ein anderer Effekt ein, dem ich mich in diesem Artikel etwas intensiver widmen werde: Kostbarer Platz im Buffer Pool wird verschwendet.

Der Page Split im Detail

Einen Page Split kann man sehr gut mit Hilfe des Transaktionsprotokolls beobachten und bereits die Analyse des Transaktionsprotokolls zeigt deutlich, wie “kostspielig”  diese Operation ist. Als Grundlage dient die folgende einfache Relation mit ein paar Attributen (Der nachfolgende Versuch wird in einer Datenbank mit dem Wiederherstellungsmodell SIMPLE durchgeführt!):

CREATE TABLE dbo.foo
(
    id
    uniqueidentifier NOT NULL  DEFAULT (newid()),
    col1 
char(100)        NOT NULL,
    col2 
char(100)        NOT NULL,
    col3 
datetime         NOT NULL  DEFAULT (getdate()),

    CONSTRAINT pk_foo_id PRIMARY KEY CLUSTERED (id)
);

-- Löschen des Transaktionsprotokolls
CHECKPOINT;

Nachdem die Relation erstellt wurde, wird das Transaktionsprotokoll zurück gesetzt und nun kann die Relation mit ein paar Daten gefüllt werden. Bei der Definition der Relation wurde der Clustered Key auf das Attribut [Id] gelegt. Dieses Attribut hat den denkbar ungünstigsten Datentypen (Länge) und Standardwert (newid), der für einen Clustered Index definiert werden kann. Weitere Informationen darüber können im Artikel “Idealer Datentyp für Clustered Index – GUID vs. INT” nachlesen. Das Eintragen der Daten erledigt das anschließende Script:

DECLARE @i int = 1
WHILE @i <= 10000
BEGIN
    INSERT INTO dbo.foo
    (col1, col2)

    SELECT 'col1: ' + CAST(@i % 100 AS varchar(20)),
          
'col2: ' + CAST(@i % 50 AS varchar(20))


    SET    @i += 1 END
GO

Nachdem 10.000 Datensätze eingetragen worden sind, wird zunächst überprüft, wie häufig während des Datentransfers ein Page Split aufgetreten ist. Aufschluss darüber gibt die folgende Abfrage:

SELECT Operation,
       COUNT_BIG(*)
FROM   sys.fn_dbLog(NULL, NULL)
WHERE  Operation = 'LOP_DELETE_SPLIT'
GROUP BY
       Operation;

In meinem Beispiel wurden insgesamt 434 Page Split-Operationen durchgeführt, da die Funktion newid() Zufallswerte generiert, kann das Ergebnis an Ihrem Rechner natürlich abweichen. Interessant ist bei einem Page Split neben der Häufigkeit des Auftretens aber auch der Aufwand, den Microsoft SQL Server für jeden einzelnen Page Split betreiben muss. Also schauen wir uns die Einzelaktionen des kompletten Einfügevorgangs in etwas verkürzter Form einmal an:

SELECT [Current LSN],
       Operation,
       Context,
       AllocUnitName,
       [Page ID],
       [Slot ID],
       [New Split Page],
       [Bytes Freed]
FROM   sys.fn_dblog(NULL, NULL)
WHERE  Context != 'LCX_NULL'
ORDER BY
       [Current LSN];

Bereits nachdem die erste Datenseite erstellt wurde (Zeile 9), beginnt Microsoft SQL Server mit dem Eintragen der Datensätze. Die ersten beiden Datensätze werden noch ohne Probleme eingetragen, jedoch kann man bereits ab Datensatz 3 (Zeile 13) deutlich erkennen, dass durch die zufällige Generation von Zufallswerten eine Neuordnung in den Slots vorgenommen werden muss. Diese Operation ist nicht wirklich zeitkritisch und kann vernachlässigt werden, da die Daten nicht physikalisch eingeordnet werden sondern mit Hilfe der Slots auf die Offsets verwiesen wird. Die nachfolgende Abbildung demonstriert das Verhalten im Detail.

Die Daten befinden sich auf der Datenseite 0x19F = 415. Der Hexdump dieser Datenseite beinhaltet am Ende die Slots und die entsprechenden Offsets der eigentlichen Daten:

DBCC TRACEON (3604); DBCC PAGE ('db_demo', 1, 415, 2);

Man kann aus der Abbildung sehr deutlich entnehmen, dass die Offsets zu jeden Slot nicht fortlaufend angeordnet sind. Die Daten werden also fortlaufend auf der Datenseite gespeichert und der SLOT speichert den Offset des Datensatzes. Im weiteren Verlauf des Transaktionsprotokolls kommt es aber spätestens dann, wenn ein Datensatz nicht mehr auf die Datenseite passt, zum Page Split.

Der Auszug aus dem Transaktionsprotokoll beschreibt die Einzelschritte während eines Page Splits. Nachdem eine neue Datenseite angelegt worden ist (Zeile 47 – 61) wurden von der Seite 425 insgesamt 18 Datensätze ab Slot 16 auf die neu angelegte Seite 0x841 = 2113 verschoben. Dadurch wurde etwa die Hälfte der Datenmenge (4.194 Bytes) verschoben. Folgt man dann innerhalb des Transaktionsprotokolls weiter, kann man erkennen, dass Datensätze entweder auf Datenseite 425 oder 2113 gespeichert werden, bis der vorhandene Platz nicht mehr ausreicht und somit erneut ein – kostspieliger – Page Split auftritt.

Die richtige Auswahl eines FILLFACTOR kann dieses Phänomen abmildern. Um den Einfluss von FILLFACTOR beim Eintragen von Datensätzen zu messen, werden vier mal erneut 2.000 Datensätze in zwei Operationen á 1.000 Datensätzen in die Relation eingetragen. Zuvor wird jedoch jedes Mal der Clustered Index mit einem um 10% reduzierten FILLFACTOR neu erstellt.

Grundlagen der Option FILLFACTOR

Wie bereits oben beschrieben, ist die Option FILLFACTOR ein sehr gutes Korrektiv für OLTP-Systeme. Der Wert für FILLFACTOR kann zwischen 0 und 100 liegen und gibt die Prozentzahl an, bis zu der eine Indexseite aufgefüllt werden soll; 0 und 100 bewirken einen vollständige Nutzung der Datenseiten. Um den FILLFACTOR einzustellen, gibt es mehrere Möglichkeiten. Die erste – und sicherlich suboptimalste – Möglichkeit besteht darin, einen serverweiten FILLFACTOR zu bestimmen. Dieser Wert kann entweder über die GUI oder aber mittels sp_configure eingestellt werden.

Über T-SQL wird der Wert mittels sp_configure eingestellt:

Da es sich bei dieser Konfigurationsoption um eine erweitere Konfigurationseinstellung handelt, müssen zunächst die erweiterten Konfigurationsoptionen aktiviert werden

EXEC sp_configure 'show advanced options', 1
RECONFIGURE;

EXEC sp_configure 'fill factor (%)', '80';
RECONFIGURE WITH OVERRIDE;

Um den FILLFACTOR für jeden Index individuell anzugeben, kann er als optionaler Parameter zu jedem ALTER INDEX … REBUILD hinzugefügt werden. Der definierte Wert gibt an, bis zu wie viel Prozent die Datenseite mit bestehenden Daten aufgefüllt werden soll! Dem Ergebnis liegt das folgende Testszenario zu Grunde:

  • Für jeden Messvorgang wird die Relation dbo.foo vollständig neu erstellt und 10.000 Datensätze eingetragen.
  • Anschließend wird der Clustered Index mit dem jeweiligen FILLFACTOR neu aufgebaut
  • Nachdem der Index neu aufgebaut wurde, wurden in jeweils zwei separaten Durchgängen erneut jeweils 1.000 neue Datensätze hinzugefügt.
  • Für jede Messung wurden explizit die Anzahl der Page Splits gemessen

FILLFACTOR

Messung 1

Messung 2

90%

196

287

80%

13

127

70%

0

21

60%

0

0

50%

0

0

Die Analyse zeigt, dass bei der Konstellation eines zufällig generierten Clustered Key die Häufigkeit von Page Splits mit abnehmenden FILLFACTOR abnimmt. Das in der zweiten Messung noch einmal ein Sprung der Page Splits zu verzeichnen ist, liegt daran, dass während der ersten Messung viele Seiten durch die neuen 1.000 Datensätze aufgefüllt wurden und dann im zweiten Durchlauf den Page Split erfahren mussten. Ab 70% FILLFACTOR waren diese Page Splits aber schon fast zu vernachlässigen.

Das Ergebnis zeigt aber bereits deutlich, dass ab ca. 2.000 Eintragungen die Page Splits wieder zunehmen. Sind also pro Tag zwischen 1.000 – 2.000 Aktivitäten zu verzeichnen, sollte für diese Relation ein FILLFACTOR von <=70% verwendet werden, und der Index jeden Abend neu erstellt werden. Dadurch ließen sich die Page Splits deutlich vermeiden.

Ganz anders sieht das Ergebnis jedoch aus, wenn der Clustered Key ein fortlaufender Wert ist. Um hier gleiche Voraussetzungen zu verschaffen, wird statt newid() als Standardwert newsequentialid() verwendet. Diese Funktion generiert fortlaufende GUIDS während des Einfügevorgangs. Die Messungen für newsequentialid() kann man vernachlässigen; lediglich bei einem FILLFACTOR von 100% gab es 1 Page Split. Eintragungen “zwischen” zwei Datensätzen kann es nicht geben (na ja – bei einem Neustart des Servers kann das sehr wohl passieren, gehört aber nicht zu diesem Thema).

Zwischenergebnis

Der FILLFACTOR ist ein sehr gutes Korrektiv für OLTP-Systeme, um Page Splits zu vermeiden. Diese Strategie ist aber nur dann sinnvoll, wenn häufig neue Indexwerte zwischen bereits bestehenden Indexwerten eingetragen werden müssen. Sind die Indexwerte fortlaufender Natur, macht ein FILLFACTOR keinen Sinn.

Verhalten von FILLFACTOR auf den Buffer Pool

Der Hauptzweck einer Microsoft SQL Server-Datenbank ist das Speichern und Abrufen von Daten. Daher stellt ein hohes I/O auf dem Datenträger ein Hauptmerkmal dar. Physikalische I/O-Vorgänge beanspruchen viele Ressourcen und benötigen relativ viel Zeit für die Ausführung. Daher ist Microsoft SQL Server so konzipiert, dass teure physikalische I/O-Operationen vermieden werden, indem die Index- und Datenseiten beim ersten Zugriff in den Buffer Pool im Arbeitsspeicher geladen werden. Ein “Buffer” entspricht exakt der Definition einer Index- / Datenseite von 8.192 Bytes; ein “Buffer” speichert also immer eine Index- / Datenseite! Sehr wichtig in diesem Zusammenhang ist die Tatsache, dass immer eine vollständige Index- / Datenseite pro Buffer belegt wird. Es können keine zwei Seiten gemeinsam einen Buffer teilen!

Ist ein Index stark fragmentiert wird der Buffer Pool zwar mit Index- / Datenseiten gefüllt aber da die Seiten nicht vollständig gefüllt sind, wird kostbarer Arbeitsspeicher verschwendet. Bleiben wir bei dem obigen Beispiel und schauen uns die Belegung des Buffer Pools nach dem Eintragen von 10.000 Datensätzen und einem anschließenden REBUILD des Clustered Index an. Die Relation wird wieder mit einer “random” GUID als Clustered Key erstellt und erneut 10.000 Datensätze eingetragen. Die physikalische Struktur des Index sieht anschließend so aus:

SELECT OBJECT_NAME(i.object_id)      AS table_name,
       i.name
                        AS index_name,
       ps.index_type_desc,
       ps.avg_fragmentation_in_percent,
       ps.fragment_count,
       ps.page_count,
       ps.avg_page_space_used_in_percent,
       ps.record_count

FROM   sys.indexes i INNER JOIN sys.dm_db_index_physical_stats(db_id(), OBJECT_ID('dbo.foo'), DEFAULT, DEFAULT, 'DETAILED') ps
       ON (
           i.object_id = ps.object_id AND
           i.index_id = ps.index_id
          )

ORDER BY
       ps.index_level

Der Index ist zu 100% fragmentiert (Jede Seite wurde durch einen Page Split geteilt). Insgesamt hat der Index eine Größe von 27 MB. Beim Zugriff auf die Daten werden die Daten in den Buffer Pool geladen. Das Ergebnis sieht wie folgt aus:

SELECT * FROM dbo.foo;
GO

SELECT OBJECT_NAME(p.object_id)                 AS object_name,
       i.name                                  
AS index_name,
       bd.page_type,
       SUM(bd.row_count)                        AS row_count,
       SUM(bd.free_space_in_bytes * 1.0 / 1024) AS free_space
FROM   sys.dm_os_buffer_descriptors bd INNER JOIN sys.allocation_units au
       ON (bd.allocation_unit_id = au.allocation_unit_id) INNER JOIN sys.partitions p
       ON (
            au.container_id =
CASE WHEN au.type IN (1, 3)
                                   THEN p.hobt_id
                                   ELSE p.partition_id
                              END
          
) INNER JOIN sys.indexes i
        ON (
             p.object_id = i.object_id AND
             p.index_id = i.index_id
           )
WHERE database_id = db_id() AND
      p.object_id = OBJECT_ID('dbo.foo')
GROUP BY
     
p.object_id,
      i.name,
      bd.page_type;

Die Abbildung zeigt für jeden Seitentypen die Gesamtzahl der Datenzeilen im Buffer Pool sowie das durch die Fragmentierung der Datenseiten zur Verfügung stehende Volumen, dass ungenutzt verschwendet wird. Das Ergebnis sieht bedeutend besser aus, wenn der Index mittels REBUILD neu aufgebaut wurde (bei FILLFACTOR = 100).

Was für ein Unterschied – aus vormals 1 MB ungenutzter Speicher im Buffer Pool sind nur noch knapp 60 KB verblieben. Insbesondere diese – wichtige- Information macht klar, wie wichtig es ist, den FILLFACTOR nicht pauschal auf alle Indexe anzuwenden sondern vielmehr zu prüfen, ob, wie häufig und wie verschieden die Daten sind, die in den Index aufgenommen werden.

Der Speicherverbrauch im Buffer Pool für die Relation dbo.foo wirkt sich je nach FILLFACTOR entsprechend aus. Die Tabelle sowie die Grafik zeigen, wie sich der Speicherverbrauch im Verhältnis zum FILLFACTOR verändert. Da musste selbst ich staunen, als ich die Ergebnisse gesehen habe!

FILLFACTOR

free_size_KB

100%

57

90%

200

80%

555

70%

887

60%

1495

50%

2120

Wie man an den Zahlen deutlich erkennen kann, sollte der Wert für einen möglichen Einsatz von FILLFACTOR sehr gut gewählt werden. Pauschalwerte führen meistens zu eher unperformanten Systemen, da viel Speicher des Buffer Pools auf Grund von fast leeren Datenseiten nicht gefüllt werden kann. Ein wichtiges Indiz für die die richtige Bewertung des Einsatzes von FILLFACTOR sind meines Erachtens folgende Parameter:

  1. Wie häufig werden Daten des Index geändert?`
  2. Sind die Schlüsselattribute des Index (also die Sortierkriterien) eher zufällig oder fortlaufend?
  3. Wie viele Datensätze werden pro Wartungsperiode zur Relation hinzu gefügt?

Indexanalyse eines bestehenden Systems

Die wohl größte Herausforderung bei der Bewertung einer FILLFACTOR-Strategie ist der periodische Workload. Während man auf Basis der Struktur der Relation schnell erkennen kann, ob z. B. der Clustered Key fortlaufende Werte generiert, ist es beim Workload schon etwas schwieriger. Meistens hilft nur ein Gespräch mit der Fachabteilung. Ist die jedoch auch nicht in der Lage, die benötigten Informationen zur Verfügung zu stellen, gibt Microsoft SQL Server selbst auch eine Möglichkeit zur Hand, um den möglichen Workload abzuschätzen. Das folgende Beispiel soll eine Analyse demonstrieren, bei der eine abschließende Strategie erläutert wird.

Gegeben ist eine Relation mit dem folgenden Aufbau (so tatsächlich aus einem Use Case entnommen!):

CREATE TABLE dbo.tbl_Addresses
(
    AId
       int          NOT NULL IDENTITY(1, 1),
    CCode
     varchar(5)   NULL,
    Street
    varchar(200) NULL,
    ZIP
       varchar(20)  NULL,
    City
      varchar(100) NULL,
    State    
varchar(50)  NULL,
    SortName
  varchar(400) NULL,

    CONSTRAINT pktblAdressen PRIMARY KEY NONCLUSTERED(AId),
    CONSTRAINT idxtblAdressen UNIQUE CLUSTERED(SortName)
)
GO

Die Relation besitzt zwei Indexe wobei der Clustered Index entgegen meiner Erwartungen NICHT auf dem Attribut [AId] sondern auf dem Attribut [SortName] liegt. Grund für diese Strategie ist gemäß Aussage der Entwickler, dass dieses Attribut dazu verwendet wird, um zu überprüfen, ob eine Adresse bereits vorhanden ist. Der SortName wird durch einen Trigger bei der Erfassung der Adressdaten generiert. Wie auch immer – entscheidend ist für dieses Problem nicht die Indexstrategie sondern eine Analyse der Indexe sowie die ideale FILLFACTOR-Strategie für die Maintenanceaufträge.

Schon beim ersten Blick ist klar erkennbar, dass der Index [pktblAdressen] mit einem FILLFACTOR = 100 neu aufgebaut wird. Das Attribut [AId] wird für den Index als Schlüsselattribut verwendet. Der Wert wird durch eine fortlaufende Nummerierung mittels IDENTITY gewährleistet. Somit werden Datensätze IMMER am Ende eingefügt. Problematischer sieht es aber bei dem Clustered Index aus. Der Clustered Index verwendet als Clustered Key das Attribut [SortName]. Dieses Attribut wird gemäß der Aussage der Entwickler beim Einfügen eines neuen Datensatzes aus allen Adresskomponenten konkateniert und eingetragen.

Die Adresse “DE | Bahnstrasse 33 | 64390 |Erzhausen | Hessen” wird durch den Trigger zu “DE#Bahnstrasse33#64390#Erzhausen#Hessen”. Wird die Adresse an einer Stelle geändert, wird sich der Clustered Key ändern und der Datensatz muss neu eingeordnet werden. Gleiches gilt natürlich auch für die Erfassung neuer Adressen. Um die richtige Strategie festzulegen, muss zunächst geprüft werden, wie fragmentiert der Index bereits ist und wie sich der Workload darstellt. Um verlässliche Ergebnisse zu bekommen, wurden in der Nacht vor der Analyse alle Indexe mit dem FILLFACTOR = 100 neu aufgebaut und für die betroffene Relation ergab sich am nächsten Abend das folgende Bild:

SELECT i.name,
       i.type_desc,
       ps.avg_fragmentation_in_percent,
       ps.page_count,
       ps.record_count
FROM   sys.indexes i INNER JOIN sys.dm_db_index_physical_stats(db_id(), object_id('dbo.tblAddresses'), DEFAULT, DEFAULT, 'DETAILED') ps
       ON (
            i.object_id = ps.object_id AND
            i.index_id = ps.index_id
           )

Eine Fragmentierung im Clustered Index war zu erwarten obwohl sie noch sehr moderat ausgefallen ist. Jedoch ist mir bei der Analyse aufgefallen, dass der NONCLUSTERED Index ebenfalls zu fast 10%  fragmentiert ist. Auf Rückfrage bestätigte man mir, dass trotz IDENTITY-Einschränkung auch schon mal manuelle AID-Werte eingetragen werden – dies ist aber eher selten der Fall. Insgesamt wurden die Indexe in nur 18 Stunden verhältnismäßig stark fragmentiert. Die Höhe der Fragmentierung allein kann aber nicht zu 100% Aufschluss über eine geeignete Strategie für den FILLFACTOR geben. Entscheidend ist immer das Verhältnis zwischen Workload und der daraus resultierenden Fragmentierung. Den Workload kann man mit der folgenden Abfrage analysieren:

SELECT i.name                    AS index_name,
       i.type_desc              
AS type_desc,
       os.leaf_insert_count,
       os.leaf_delete_count,
       os.leaf_update_count,
       os.nonleaf_insert_count,
       os.nonleaf_delete_count,
       os.nonleaf_update_count
FROM   sys.indexes i INNER JOIN sys.dm_db_index_operational_stats (db_id(), DEFAULT, DEFAULT, DEFAULT) os
       ON (
            i.object_id = os.object_id AND
            i.index_id = os.index_id
           )
INNER JOIN sys.objects o
        ON (i.object_id = o.object_id)
WHERE   o.is_ms_shipped = 0 AND
        o.name = 'tblAddresses'
ORDER BY
        o.name,
        i.index_id;

Der Clustered Index hat eine Fragmentierung von 18% bei insgesamt ca. 9.000 INSERT / UPDATE Aktionen. Verlässliche Zahlen kann nur die INSERT-Operation geben, da ein Update sich auf alle Attribute der Relation beziehen kann. Interessant ist jedoch auch das Verhältnis zwischen den INSERT-Vorgängen und dem Grad der Fragmentierung. Obwohl der SortName als Clustered Key eine höhere Dynamik besitzt als das Attribut [AID], ist das Verhältnis deutlich schlechter.

Wir haben als Strategie für den FILLFACTOR basierend auf den obigen Ergebnissen folgende implementiert:

ALTER INDEX idxtblAdressen ON dbo.tblAddresses REBUILD WITH (FILLFACTOR = 90);
ALTER INDEX pktblAdressen ON dbo.tblAddresses REBUILD WITH (FILLFACTOR = 100);

Da das Attribut [SortName] einer größeren Streuung auf Grund der Dateninhalte unterworfen ist, scheint ein FILLFACTOR von 90% ideal, da das Datenvolumen für einen Tag einen überschaubaren Rahmen besitzt. Für den Index [pktblAdressen] wurde ein FILLFACTOR von 100% festgelegt. Dieser Wert ist vertretbar, da das Schlüsselattribut fortlaufend nummeriert ist und manuelle Eintragungen eher selten sind. 

Fazit

Es gibt nur sehr wenige Fälle (ich kenne eigentlich keinen), in denen pauschale FILLFACTOR-Werte ausreichend für eine gute Performance der Datenbankanwendung sind. Weder pauschal 100% (wie sicherlich im Großteil aller Systeme!) noch 90, 80, 70,.. Prozent ein Garant für schnelle Systeme. Wir der FILLFACTOR zu klein gewählt, verschwendet man wertvollen Speicher des Buffer Pools; wird der Bereich zu groß gewählt, läuft man Gefahr einer hohen Fragmentierung, die wiederum in der Verschwendung des kostspieligen Buffer Pools endet. Nur eine dedizierte Analyse aller Indexe auf ihre Einzelfälle kann ein optimales System generieren. Je variabler die Schlüsselattribute eines Indexes sind, um so wahrscheinlicher ist eine Anpassung des FILLFACTOR nach unten. Besitzt der Schlüssel einen fortlaufenden Wert, ist sicherlich ein FILLFACTOR von 100% sinnvoll, wenn sicher gestellt werden kann, dass innerhalb der Wartungsperiode keine oder wenige neue Werte zwischen bestehenden Schlüsselwerten gespeichert werden oder die Schlüsselwerte aktualisiert werden. Ich persönlich würde nur bei Indexen auf Attributen mit der Eigenschaft IDENTITY “pauschal” einen FILLFACTOR von 100 einsetzen – alles andere unterliegt dem Einzelfall!

Herzlichen Dank fürs Lesen!

FILLFACTOR http://msdn.microsoft.com/de-de/library/ms177459.aspx
Buffer Pool http://msdn.microsoft.com/de-de/library/aa337525.aspx
sp_configure http://msdn.microsoft.com/de-de/library/ms188787.aspx
newid() http://msdn.microsoft.com/en-us/library/ms190348.aspx
newsequentialid() http://msdn.microsoft.com/de-de/library/ms189786.aspx
sys.dm_os_buffer_descriptors http://msdn.microsoft.com/de-de/library/ms173442.aspx
sys.dm_db_index_physical_stats http://msdn.microsoft.com/de-de/library/ms188917.aspx
sys.dm_db_index_operational_stats http://msdn.microsoft.com/de-de/library/ms174281.aspx