Est-ce que l'ordre des colonnes dans un index PK est important?


33

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 toutes les nuits et aucune "nouvelle" donnée n'est chargée - il s'agit d'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 que chaque RowNumber/DataDatecombinaison est unique, c’est ma clé primaire.

Je remarque que, en raison de la façon dont j'ai défini le PK dans SSMS Table Designer, RowNumberest répertorié en premier et en DataDatesecond.

Je remarque également que ma fragmentation est toujours TRES élevée ~ 99%.

Maintenant, comme chaque objet DataDaten'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 la RowNumberpremière, et donc d'un déplacement de tout le reste?


Rownumbern'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 chacun DataDate.

Exemple 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' RowNumberordre, une DataDatepar 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 il en résulte une fragmentation élevée.

Réponses:


50

Est-ce que l'ordre des colonnes dans un index PK est important?

Oui.

Par défaut, la contrainte de clé primaire est appliquée dans SQL Server par un index en cluster unique. L'index clusterisé définit l' ordre logique des lignes de la table. Un certain nombre de pages d'index supplémentaires peuvent être ajoutées pour représenter les niveaux supérieurs de l'index b-tree, mais le niveau le plus bas (feuille) d'un index en cluster correspond simplement à l'ordre logique des données.

Pour être clair, les lignes d'une page ne sont pas nécessairement stockées physiquement dans l'ordre des clés d'index en cluster. Il existe une structure distincte d'indirection dans la page qui stocke un pointeur sur chaque ligne. Cette structure est triée par les clés d'index en cluster. De plus, chaque page a un pointeur sur la page précédente et la page suivante au même niveau dans l'ordre de la clé d'index en cluster.

Avec une clé primaire en cluster de (RowNumber, DataDate), les lignes sont triées de manière logique en premier RowNumber, puis en DataDate- de manière à ce que toutes les lignes RowNumber = 1soient logiquement regroupées, puis les lignes où RowNumber = 2, etc.

Lorsque vous ajoutez de nouvelles données (avec RowNumbersde 1 à n), les nouvelles lignes appartiennent logiquement à l'intérieur des pages existantes. SQL Server devra donc probablement effectuer de nombreuses opérations de fractionnement des pages pour libérer de l'espace. Toute cette activité génère beaucoup de travail supplémentaire (y compris la consignation des modifications) sans aucun gain.

Les pages divisées démarrent également à environ 50% vides. Par conséquent, une division excessive peut entraîner une densité de page faible (moins de lignes que la valeur optimale par page). Non seulement cette mauvaise nouvelle pour la lecture à partir du disque (faible densité = plus de pages à lire), mais les pages à faible densité prennent également plus de place en mémoire lors de la mise en cache.

Si vous modifiez l’index clusterisé en (DataDate, RowNumber), cela signifie que les nouvelles données (avec, vraisemblablement, plus DataDatesque ce qui est actuellement stocké) sont ajoutées à la fin logique de l’index clusterisé sur les nouvelles pages. Cela supprime les frais généraux inutiles liés au fractionnement des pages et accélère les temps de chargement. Des données moins fragmentées signifient également qu'une activité de lecture à l'avance (lire des pages d'un disque juste avant de les utiliser pour une requête en cours) peut être plus efficace.

Si rien d'autre, vos questions sont beaucoup plus susceptibles de rechercher DataDateque RowNumber. Un index en cluster sur (DataDate, RowNumber) prend en charge la recherche d’index sur DataDate(puis RowNumber). Le dispositif existant ne prend en charge que les recherches sur RowNumber(et seulement alors, peut-être sur DataDate). Vous pourrez peut-être supprimer l'index non cluster existant DataDateune fois que la clé primaire aura été modifiée. L'index clusterisé sera plus large que l'index non clusterisé qu'il remplace. Vous devez donc effectuer un test pour vous assurer que les performances restent acceptables.

Lors de l'importation de nouvelles données avec bcp, vous pouvez obtenir de meilleures performances si les données du fichier d'importation sont triées par les clés d'index en cluster (idéalement (DataDate, RowNumber) et que vous spécifiez l' bcpoption suivante:

-h "ORDER(DataDate,RowNumber), TABLOCK"

Pour optimiser les performances de chargement des données, vous pouvez essayer de réaliser des insertions journalisées de manière minimale. Pour plus d'informations, voir:


4
Une excellente réponse - je sais maintenant ce que je devrais faire ET pourquoi. Je l'avais pensé, mais pas connu donc! Merci.
BlueChippy

Il a fallu un bon bout de temps pour insérer la base de données dans mon serveur SQL Server local: Avant de modifier le chargement de l'index, il fallait 45 minutes ... après, il ne fallait que 5 minutes !!!
BlueChippy

13

Oui, la commande est critique. Je doute fortement que vous ayez déjà interrogé RowNumber (par exemple WHERE RowNumber=1). La plupart 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 avant le DataDate.

La fragmentation est en général un rouge-hareng. Réduire la fragmentation ne devrait pas être votre objectif ici, mais une organisation appropriée de vos requêtes devrait l'être. Obtenir une fragmentation réduite en plus est une bonne idée, mais ce n'est pas un objectif en soi. Si votre modèle de données bien organisé correspond à votre charge de travail (vos requêtes sont correctement traitées) et que vous avez des mesures montrant que la fragmentation a un impact sur les performances, nous pouvons en parler.


J'ai aussi un index non-cluster sur DataDate, qui, comme vous dites, est souvent une WHEREclause dans les requêtes.
BlueChippy

1
Si ORDER des colonnes est critique, l’impact de l’ordre incorrects verra-t-il mon I / O augmenter? Je pense que c'est une commande par RowNumber et donc que je dois faire beaucoup de travail sur les index à chaque fois, alors que cela devrait être basé sur DataDate?
BlueChippy
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.