Indexation d'un GUID PK dans SQL Server 2012

13

Mes développeurs ont configuré leur application pour utiliser les GUID en tant que PK pour à peu près toutes leurs tables et par défaut, SQL Server a configuré l'index cluster sur ces PK.

Le système est relativement jeune et nos plus grandes tables ne dépassent pas un million de lignes, mais nous examinons notre indexation et voulons pouvoir évoluer rapidement car cela pourrait être nécessaire dans un proche avenir.

Donc, ma première inclination a été de déplacer l'index clusterisé vers le champ créé qui est une représentation bigint d'un DateTime. Cependant, la seule façon dont je peux rendre le CX unique serait d'inclure la colonne GUID dans ce CX, mais l'ordre en le créant d'abord.

Cela rendrait-il la clé de clustering trop large et augmenterait-il les performances d'écriture? Les lectures sont également importantes, mais les écritures sont probablement une préoccupation plus importante à ce stade.

njkroes
la source
1
Comment les GUID sont-ils générés? NEWID ou NEWSEQUENTIALID?
swasheck
6
Les performances de guidage et d'insertion en cluster ne devraient figurer dans une phrase que si le mot précédant immédiatement "performance" est minimisé
billinkc
2
Sortez ces développeurs pour le déjeuner et expliquez-leur que s'ils utilisent à nouveau NEWID () comme clé primaire, vous leur reprocherez de mauvaises performances. Ils vous demanderont très rapidement quoi faire pour éviter cela. À ce stade, vous dites plutôt utiliser IDENTITY (1,1). (peut-être une légère simplification excessive mais 9 fois sur 10 qui fonctionneront).
Max Vernon
3
La raison de notre haine des guid est qu'ils sont larges (16 octets) et lorsqu'ils ne newsequentialidsont pas créés avec sont aléatoires. Les clés en cluster sont meilleures lorsqu'elles sont étroites et croissantes. Un GUID est le contraire: gras et aléatoire. Imaginez une étagère presque pleine de livres. En vient l'OED et en raison du caractère aléatoire des guides, il s'insère au milieu de l'étagère. Pour garder les choses ordonnées, la bonne moitié des livres doit être placée dans un nouvel emplacement, ce qui est une tâche exigeante en temps. C'est ce que le GUID fait à votre base de données et réduit les performances.
billinkc
7
Le moyen de résoudre le problème de l'utilisation d'identificateurs uniques consiste à revenir à la planche à dessin et à ne pas utiliser les identificateurs uniques . Ils ne sont pas terribles si le système est petit, mais si vous avez au moins quelques millions de tables de lignes + (ou n'importe quelle table plus grande que cela), vous allez carrément vous écraser en utilisant des identificateurs uniques pour les clés.
Jon Seigel

Réponses:

20

Les principaux problèmes avec les GUID, en particulier ceux non séquentiels, sont:

  • Taille de la clé (16 octets contre 4 octets pour un INT): cela signifie que vous stockez 4 fois la quantité de données dans votre clé avec cet espace supplémentaire pour tous les index s'il s'agit de votre index cluster.
  • Fragmentation d'index: il est pratiquement impossible de conserver une colonne GUID non séquentielle défragmentée en raison de la nature complètement aléatoire des valeurs de clé.

Alors qu'est-ce que cela signifie pour votre situation? Cela dépend de votre conception. Si votre système concerne simplement les écritures et que vous ne vous souciez pas de la récupération des données, l'approche décrite par Thomas K est exacte. Cependant, vous devez garder à l'esprit qu'en poursuivant cette stratégie, vous créez de nombreux problèmes potentiels pour la lecture de ces données et leur stockage. Comme le souligne Jon Seigel , vous occuperez également plus d'espace et aurez essentiellement un ballonnement de mémoire.

La principale question concernant les GUID est de savoir à quel point ils sont nécessaires. Les développeurs les aiment parce qu'ils garantissent l'unicité globale, mais c'est une occasion rare que ce type d'unicité soit nécessaire. Mais considérez que si votre nombre maximal de valeurs est inférieur à 2 147 483 647 (la valeur maximale d'un entier signé de 4 octets), vous n'utilisez probablement pas le type de données approprié pour votre clé. Même en utilisant BIGINT (8 octets), votre valeur maximale est de 9 223 372 036 854 775 807. Cela est généralement suffisant pour toute base de données non globale (et de nombreuses bases de données globales) si vous avez besoin d'une valeur d'incrémentation automatique pour une clé unique.

Enfin, en ce qui concerne l'utilisation d'un segment de mémoire par rapport à un index clusterisé, si vous écrivez uniquement des données, un segment de mémoire serait plus efficace car vous réduisez la surcharge pour les insertions. Cependant, les tas dans SQL Server sont extrêmement inefficaces pour la récupération de données. D'après mon expérience, un index cluster est toujours souhaitable si vous avez la possibilité d'en déclarer un. J'ai vu l'ajout d'un index clusterisé à une table (4 milliards + d'enregistrements) améliorer les performances de sélection globales d'un facteur 6.

Information additionnelle:

Mike Fal
la source
13

Il n'y a rien de mal avec GUID en tant que clés et clusters dans un système OLTP (sauf si vous avez BEAUCOUP d'index sur la table qui souffrent de l'augmentation de la taille du cluster). En fait, ils sont beaucoup plus évolutifs que les colonnes IDENTITY.

Il y a une croyance répandue que les GUID sont un gros problème dans SQL Server - en grande partie, c'est tout simplement faux. En fait, le GUID peut être considérablement plus évolutif sur les boîtes avec plus d'environ 8 cœurs:

Je suis désolé, mais vos développeurs ont raison. Souciez-vous d'autres choses avant de vous soucier du GUID.

Oh, et enfin: pourquoi voulez-vous un index de cluster en premier lieu? Si votre problème est un système OLTP avec beaucoup de petits index, vous êtes probablement mieux avec un tas.

Voyons maintenant ce que la fragmentation (que le GUID introduira) fait à vos lectures. Il y a trois problèmes majeurs avec la fragmentation:

  1. La page divise les E / S du disque de coût
  2. Les demi-pages pleines ne sont pas aussi efficaces en mémoire que les pages complètes
  3. Cela entraîne le stockage des pages dans le désordre, ce qui rend les E / S séquentielles moins probables

Étant donné que votre préoccupation dans la question concerne l'évolutivité, que nous pouvons définir comme «l'ajout de matériel accélère le système», ce sont les moindres problèmes. Pour aborder chacun à son tour

Annonce 1) Si vous voulez évoluer, vous pouvez vous permettre d'acheter des E / S. Même un SSD Samsung / Intel 512 Go bon marché (à quelques USD / Go) vous permettra de dépasser les 100 000 IOPS. Vous ne consommerez pas cela de sitôt sur un système à 2 prises. Et si vous rencontrez cela, achetez-en un de plus et vous êtes prêt

Annonce 2) Si vous supprimez votre tableau, vous aurez quand même des pages à moitié pleines. Et même si vous ne le faites pas, la mémoire est bon marché et pour tous, sauf les plus grands systèmes OLTP - les données chaudes devraient y tenir. La recherche de plus de données dans des pages est une sous-optimisation lorsque vous recherchez une échelle.

Annonce 3) Une table construite à partir de données fréquemment fragmentées et très fragmentées effectue des E / S aléatoires exactement à la même vitesse qu'une table remplie séquentiellement

En ce qui concerne la jointure, il existe deux principaux types de jointures que vous êtes susceptible de voir dans une charge de travail de type OLTP: Hash and loop. Regardons chacun à son tour:

Jointure par hachage: une jointure par hachage suppose que la petite table est analysée et que la plus grande est généralement recherchée. Les petites tables sont très probablement en mémoire, donc les E / S ne sont pas votre problème ici. Nous avons déjà évoqué le fait que les recherches ont le même coût dans un indice fragmenté que dans un indice non fragmenté

Jointure de boucle: la table externe sera recherchée. Même coût

Vous pouvez également avoir beaucoup de mauvaises analyses de table en cours - mais le GUID n'est à nouveau pas votre problème, une bonne indexation l'est.

Maintenant, vous pouvez avoir des analyses de plage légitimes en cours (en particulier lors de la jonction sur des clés étrangères) et dans ce cas, les données fragmentées sont moins "compressées" par rapport aux données non fragmentées. Mais considérons les jointures que vous verrez probablement dans des données 3NF bien indexées:

  1. Une jointure d'une table qui a une référence de clé étrangère à la clé primaire de la table qu'elle référence

  2. L'inverse

Annonce 1) Dans ce cas, vous allez pour une seule recherche à la clé primaire - joindre n à 1. Fragmentation ou non, même coût (une recherche)

Annonce 2) Dans ce cas, vous vous joignez à la même clé, mais vous pouvez récupérer plusieurs lignes (recherche de plage). La jointure dans ce cas est de 1 à n. Cependant, la table étrangère que vous recherchez, vous recherchez la même clé, qui est tout aussi susceptible d'être sur la même page dans un index fragmenté que sur une index non fragmentée.

Considérez ces clés étrangères pendant un moment. Même si vous aviez "parfaitement" séquentiellement posé nos clés primaires - tout ce qui pointe vers cette clé sera toujours non séquentiel.

Bien sûr, vous exécutez peut-être une machine virtuelle dans un SAN dans une banque peu onéreuse et gourmande en processus. Ensuite, tous ces conseils seront perdus. Mais si tel est votre monde, l'évolutivité n'est probablement pas ce que vous recherchez - vous recherchez des performances et une vitesse / coût élevés - qui sont deux choses différentes.

Thomas Kejser
la source
1
Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
Paul White 9
5

Thomas: certains de vos points sont parfaitement logiques et je suis d'accord avec eux tous. Si vous utilisez des SSD, l'équilibre de ce que vous optimisez change. Random vs séquentiel n'est pas la même discussion que le disque en rotation.

Je suis particulièrement d'accord que prendre une vue DB pure est horriblement mauvais. Rendre votre application lente et non évolutive pour améliorer uniquement les performances de la base de données peut être assez erroné.

Le gros problème avec IDENTITY (ou séquence, ou tout ce qui est généré dans la base de données) est qu'il est horriblement lent car il nécessite un aller-retour vers la base de données pour créer une clé, ce qui crée automatiquement un goulot d'étranglement dans votre base de données, il impose que les applications doivent effectuer un appel DB pour commencer à utiliser une clé. La création d'un GUID résout ce problème en utilisant l'application pour créer la clé, il est garanti d'être globalement unique (par définition), et les couches d'application peuvent ainsi l'utiliser pour transmettre l'enregistrement AVANT d'engager un aller-retour DB.

Mais j'ai tendance à utiliser une alternative aux GUID Ma préférence personnelle pour un type de données ici est un BIGINT unique au monde généré par l'application. Comment procéder? Dans l'exemple le plus trivial, vous ajoutez une petite fonction TRÈS légère à votre application pour hacher un GUID. En supposant que votre fonction de hachage est rapide et relativement rapide (voir CityHash de Google pour un exemple: http://google-opensource.blogspot.in/2011/04/introducing-cityhash.html - assurez-vous que toutes les étapes de compilation sont correctes, ou la variante FNV1a de http://tools.ietf.org/html/draft-eastlake-fnv-03 pour le code simple), vous bénéficiez à la fois des identifiants uniques générés par l'application et d'une valeur de clé 64 bits avec laquelle les processeurs fonctionnent mieux avec .

Il existe d'autres façons de générer des BIGINT, et dans ces deux algues, il existe un risque de collision de hachage - lisez et prenez des décisions conscientes.

Mark Stacey
la source
2
Je vous suggère de modifier votre réponse comme réponse à la question du PO et non (comme c'est le cas actuellement) comme réponse à la réponse de Thomas. Vous pouvez toujours mettre en évidence les différences entre Thomas (, MikeFal's) et votre suggestion.
ypercubeᵀᴹ
2
Veuillez répondre à votre question. Si vous ne le faites pas, nous le supprimerons pour vous.
JNK
2
Merci pour les commentaires Mark. Lorsque vous modifiez votre réponse (ce qui, je pense, fournit un très bon contexte), je changerais une chose: l'IDENTITÉ ne nécessite pas un aller-retour supplémentaire vers le serveur si vous faites attention à l'INSERT. Vous pouvez toujours retourner SCOPE_IDENTITY () dans le lot qui appelle INSERT ..
Thomas Kejser
1
En ce qui concerne "c'est horriblement lent car cela nécessite un aller-retour à la base de données pour créer une clé" - vous pouvez en saisir autant que vous en avez besoin en un aller-retour.
AK
En ce qui concerne "vous pouvez en saisir autant que vous le souhaitez en un seul voyage" - Vous ne pouvez pas le faire avec des colonnes IDENTITY ou toute autre méthode où vous utilisez essentiellement DEFAULT au niveau de la base de données.
Avi Cherry