Pourquoi la création d'un index prend-elle plus de temps après l'augmentation de la taille des colonnes?

18

Notre fournisseur a modifié la largeur des colonnes sur presque toutes les colonnes de la base de données entière. La base de données est d'environ 7 To, 9000+ tables. Nous essayons de créer un index sur une table contenant 5,5 milliards de lignes. Avant la mise à niveau du fournisseur, nous pouvions créer l'index en 2 heures. Maintenant, cela prend des jours. Ce qu'ils ont fait, c'est augmenter la taille de varchar (xx) à varchar (256). Ainsi, la plupart des colonnes étaient varchar (18) ou varchar (75), etc.

Quoi qu'il en soit, la clé primaire se compose de 6 colonnes dont la largeur combinée était de 126 caractères. Maintenant, après la mise à niveau, la clé primaire est de 1283 caractères, ce qui viole la limite de 900 caractères des serveurs SQL. La largeur de colonne de la table entière est passée d'un nombre total de varchars combinés de 1049 à un nombre total de varchars combinés de 4009.

Il n'y a pas d'augmentation des données, la table n'occupe pas plus d '«espace» qu'avant l'augmentation de la largeur des colonnes, mais les performances pour créer quelque chose d'aussi simple qu'un index prennent maintenant un temps déraisonnable.

Quelqu'un peut-il expliquer pourquoi la création et l'indexation prennent beaucoup plus de temps alors que la seule chose à faire était d'augmenter la taille des colonnes?

L'index que nous essayons de créer n'est pas clusterisé car le pk est l'index clusterisé. Après plusieurs tentatives pour créer l'index, nous avons abandonné. Je pense que cela a duré 4 ou 5 jours sans achèvement.

J'ai essayé cela dans un environnement de non-production en prenant un instantané du système de fichiers et mis la base de données sur un serveur plus silencieux.

Daniel Miller
la source

Réponses:

12

Remus a utilement souligné que la longueur maximale de la VARCHARcolonne affecte la taille de ligne estimée et donc les allocations de mémoire fournies par SQL Server.

J'ai essayé de faire un peu plus de recherche pour développer la partie "à partir de là sur les choses en cascade" de sa réponse. Je n'ai pas d'explication complète ou concise, mais voici ce que j'ai trouvé.

Script de repro

J'ai créé un script complet qui génère un faux ensemble de données sur lequel la création d'index prend environ 10 fois plus de temps sur ma machine pour la VARCHAR(256)version. Les données utilisées est exactement la même, mais la première table utilise les longueurs réelles de max 18, 75, 9, 15, 123, et 5, tandis que toutes les colonnes utilisent une longueur maximale de 256la seconde table.


Clé de la table d'origine

Nous voyons ici que la requête d'origine se termine en environ 20 secondes et que les lectures logiques sont égales à la taille de la table ~1.5GB(195K pages, 8K par page).

-- CPU time = 37674 ms,  elapsed time = 19206 ms.
-- Table 'testVarchar'. Scan count 9, logical reads 194490, physical reads 0
CREATE CLUSTERED INDEX IX_testVarchar
ON dbo.testVarchar (s1, s2, s3, s4)
WITH (MAXDOP = 8) -- Same as my global MAXDOP, but just being explicit
GO


Clé de la table VARCHAR (256)

Pour le VARCHAR(256)tableau, nous voyons que le temps écoulé a considérablement augmenté.

Fait intéressant, ni le temps CPU ni les lectures logiques n'augmentent. Cela est logique étant donné que le tableau contient exactement les mêmes données, mais cela n'explique pas pourquoi le temps écoulé est tellement plus lent.

-- CPU time = 33212 ms,  elapsed time = 263134 ms.
-- Table 'testVarchar256'. Scan count 9, logical reads 194491
CREATE CLUSTERED INDEX IX_testVarchar256
ON dbo.testVarchar256 (s1, s2, s3, s4)
WITH (MAXDOP = 8) -- Same as my global MAXDOP, but just being explicit
GO


Statistiques d'E / S et d'attente: original

Si nous capturons un peu plus de détails (en utilisant p_perfMon, une procédure que j'ai écrite ), nous pouvons voir que la grande majorité des E / S est effectuée sur le LOGfichier. Nous voyons une quantité relativement modeste d'E / S sur le réel ROWS(le fichier de données principal), et le type d'attente principal est LATCH_EX, indiquant un conflit de page en mémoire.

Nous pouvons également voir que mon disque en rotation se situe quelque part entre «mauvais» et «choquant mauvais», selon Paul Randal :)

entrez la description de l'image ici


E / S et statistiques d'attente: VARCHAR (256)

Pour la VARCHAR(256)version, les statistiques d'E / S et d'attente sont complètement différentes! Ici, nous voyons une énorme augmentation des E / S sur le fichier de données ( ROWS), et les temps de décrochage font maintenant que Paul Randal dit simplement "WOW!".

Il n'est pas surprenant que le type d'attente n ° 1 soit maintenant IO_COMPLETION. Mais pourquoi tant d'E / S sont-elles générées?

entrez la description de l'image ici


Plan de requête réel: VARCHAR (256)

Du plan de requête, nous pouvons voir que l' Sortopérateur a un déversement récursif (5 niveaux de profondeur!) Dans la VARCHAR(256)version de la requête. (Il n'y a aucun déversement dans la version originale.)

entrez la description de l'image ici


Progression de la requête en direct: VARCHAR (256)

Nous pouvons utiliser sys.dm_exec_query_profiles pour afficher la progression des requêtes en direct dans SQL 2014+ . Dans la version originale, l'ensemble Table Scanet Sortsont traités sans aucun déversement ( spill_page_countreste 0tout au long).

Dans la VARCHAR(256)version, cependant, nous pouvons voir que les déversements de pages s'accumulent rapidement pour l' Sortopérateur. Voici un instantané de la progression de la requête juste avant la fin de la requête. Les données ici sont agrégées sur tous les threads.

entrez la description de l'image ici

Si je fouille dans chaque fil individuellement, je vois que 2 fils terminent le tri en 5 secondes environ (@ 20 secondes au total, après 15 secondes passées sur l'analyse de la table). Si tous les threads avaient progressé à ce rythme, la VARCHAR(256)création de l' index se serait terminée à peu près en même temps que la table d'origine.

Cependant, les 6 threads restants progressent à un rythme beaucoup plus lent. Cela peut être dû à la façon dont la mémoire est allouée et à la manière dont les threads sont bloqués par les E / S car ils déversent des données. Je n'en suis pas sûr cependant.

entrez la description de l'image ici


Que pouvez-vous faire?

Il y a un certain nombre de choses que vous pourriez envisager d'essayer:

  • Collaborez avec le fournisseur pour revenir à une version précédente. Si ce n'est pas possible, indiquez au fournisseur que vous n'êtes pas satisfait de cette modification afin qu'il puisse envisager de la rétablir dans une version ultérieure.
  • Lorsque vous ajoutez votre index, pensez à utiliser OPTION (MAXDOP X)Xest un nombre inférieur à votre paramètre de niveau serveur actuel. Lorsque j'ai utilisé OPTION (MAXDOP 2)cet ensemble de données spécifique sur ma machine, la VARCHAR(256)version s'est terminée en 25 seconds(par rapport à 3-4 minutes avec 8 threads!). Il est possible que le comportement de déversement soit exacerbé par un parallélisme plus élevé.
  • Si un investissement matériel supplémentaire est possible, profilez les E / S (le goulot d'étranglement probable) sur votre système et envisagez d'utiliser un SSD pour réduire la latence des E / S occasionnées par les déversements.


Lectures complémentaires

Paul White a un joli billet de blog sur les éléments internes des types SQL Server qui peuvent être intéressants. Il parle un peu de débordement, de biais de thread et d'allocation de mémoire pour les tris parallèles.

Geoff Patterson
la source
11

La table de tri intermédiaire sera estimée différemment entre les deux cas. Cela conduira à différentes demandes d'allocation de mémoire ( VARCHAR(256)sera plus importante) et probablement une allocation réelle beaucoup plus petite, en pourcentage, par rapport à la demande «idéale». Je suppose que cela entraîne des déversements lors du tri.

En testant le script de Geoff (sur 100 000 lignes uniquement), je peux clairement voir la différence dans la taille estimée des lignes (141B contre 789B). De cela sur les choses en cascade.

Remus Rusanu
la source
8
Je suis sûr que Paul apportera une réponse plus approfondie et complète, y compris des piles d'appels, qui seront utilisées comme matériel d'apprentissage par l'équipe de développement. Encore une fois ...
Remus Rusanu