J'ai quelques très grandes tables avec la même structure de base. Chacun a une colonne RowNumber (bigint)
et DataDate (date)
. Les données sont chargées à l'aide de SQLBulkImport tous les soirs, et aucune "nouvelle" donnée n'est jamais chargée - c'est un enregistrement historique (SQL Standard, pas Enterprise, donc pas de partitionnement).
Parce que chaque bit de données doit être lié à d'autres systèmes, et chaque RowNumber/DataDate
la combinaison est unique, c'est ma clé primaire.
Je remarque qu'en raison de la façon dont j'ai défini le PK dans SSMS Table Designer, RowNumber
est répertorié en premier et DataDate
en second.
Je remarque également que ma fragmentation est toujours TRÈS élevée ~ 99%.
Maintenant, comme chaque DataDate
n'apparaît qu'une seule fois, je m'attendrais à ce que l'indexeur ajoute simplement aux pages chaque jour, mais je me demande s'il s'agit en fait d'une indexation basée sur RowNumber
en premier, et donc d'avoir à déplacer tout le reste?
Rownumber
n'est pas une colonne d'identité, c'est un int généré par un système externe (malheureusement). Il se réinitialise au début de chaque DataDate
.
Exemples de données
RowNumber | DataDate | a | b | c.....
1 |2013-08-01| x | y | z
2 |2013-08-01| x | y | z
...
1 |2013-08-02| x | y | z
2 |2013-08-02| x | y | z
...
Les données sont chargées dans l'ordre RowNumber
, une DataDate
par chargement.
Le processus d'importation est bcp - J'ai essayé de charger dans une table temporaire puis de sélectionner dans l'ordre à partir de là (ORDER BY RowNumber, DataDate
) mais en ressort toujours une fragmentation élevée.
L'ordre des colonnes dans un index PK est-il important?
Oui.
Par défaut, la contrainte de clé primaire est appliquée dans SQL Server par un index cluster unique. L'index cluster définit l'ordre logique des lignes de la table. Il peut y avoir un certain nombre de pages d'index supplémentaires ajoutées pour représenter les niveaux supérieurs de l'index b-tree, mais le niveau le plus bas (feuille) d'un index cluster est simplement l'ordre logique des données elles-mêmes.
Pour être clair à ce sujet, les lignes d'une page ne sont pas nécessairement physiquement stockées dans l'ordre des clés d'index cluster. Il existe une structure d'indirection distincte dans la page qui stocke un pointeur sur chaque ligne. Cette structure est triée par les clés d'index cluster. En outre, chaque page a un pointeur vers la page précédente et suivante au même niveau dans l'ordre des clés d'index cluster.
Avec une clé primaire en cluster de (RowNumber, DataDate)
, les lignes sont triées logiquement d'abord par RowNumber
puis par DataDate
- donc toutes les lignes où RowNumber = 1
sont logiquement regroupés, puis les lignes où RowNumber = 2
etc.
Lorsque vous ajoutez de nouvelles données (avec RowNumbers
de 1 à n), les nouvelles lignes appartiennent logiquement à l'intérieur des pages existantes, donc SQL Server devra probablement faire beaucoup de travail en fractionnant les pages pour faire de la place. Toute cette activité génère beaucoup de travail supplémentaire (y compris l'enregistrement des modifications) sans aucun gain.
Les pages fractionnées commencent également à environ 50% de vide, de sorte qu'un fractionnement excessif peut également entraîner une faible densité de page (moins de lignes que optimal par page). Non seulement c'est une mauvaise nouvelle pour la lecture à partir du disque (densité plus faible = plus de pages à lire), mais les pages à faible densité prennent également plus de place en mémoire lorsqu'elles sont mises en cache.
Modification de l'index clusterisé sur (DataDate, RowNumber
) signifie que de nouvelles données (avec, vraisemblablement, un DataDates
supérieur à celui actuellement stocké) sont ajoutées à la fin logique de l'index cluster sur les pages fraîches. Cela supprimera les frais généraux inutiles de la division des pages et entraînera des temps de chargement plus rapides. Des données moins fragmentées signifient également que l'activité de lecture anticipée (lecture des pages du disque juste avant qu'elles ne soient nécessaires pour une requête en cours) peut être plus efficace.
Si rien d'autre, vos requêtes sont beaucoup plus susceptibles de rechercher sur DataDate
que RowNumber
. Un index cluster sur (DataDate, RowNumber
) prend en charge les recherches d'index sur DataDate
(puis RowNumber
). L'arrangement existant ne supporte que les recherches sur RowNumber
(et alors seulement, peut-être, sur DataDate
). Vous pourrez peut-être supprimer l'index non cluster existant sur DataDate
une fois la clé primaire modifiée. L'index clusterisé sera plus large que l'index non cluster qu'il remplace, vous devez donc tester pour vous assurer que les performances restent acceptables.
Lorsque vous importez de nouvelles données avec bcp
, vous pouvez obtenir des performances supérieures si les données du fichier d'importation sont triées par les clés d'index en cluster (idéalement (DataDate, RowNumber
)) et vous spécifiez l'option bcp
:
-h "ORDER(DataDate,RowNumber), TABLOCK"
Pour de meilleures performances de chargement des données, vous pouvez essayer de réaliser des insertions à journalisation minimale. Pour plus d'informations, voir:
Oui, la commande est critique. Je doute fortement que vous ayez interrogé par RowNumber (par exemple WHERE RowNumber=1
). La très grande majorité des séries chronologiques sont interrogées par date (WHERE DataDate BEWEEN @start AND @end
) et de telles requêtes nécessiteraient une organisation en cluster par DataDate
.
La fragmentation en général est un hareng rouge. Réduire la fragmentation ne devrait pas être votre objectif ici, mais avoir une organisation appropriée pour vos requêtes. En plus, réduire la fragmentation est une bonne idée, mais ce n'est pas un objectif en soi. Si vous avez un modèle de données correctement organisé qui correspond à votre charge de travail (vos requêtes sont correctement couvertes) et vous avez des mesures qui montrent que la fragmentation a un impact sur les performances, nous pouvons en parler.