Remus a utilement souligné que la longueur maximale de la VARCHAR
colonne 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 256
la 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 LOG
fichier. 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 :)
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?
Plan de requête réel: VARCHAR (256)
Du plan de requête, nous pouvons voir que l' Sort
opé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.)
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 Scan
et Sort
sont traités sans aucun déversement ( spill_page_count
reste 0
tout au long).
Dans la VARCHAR(256)
version, cependant, nous pouvons voir que les déversements de pages s'accumulent rapidement pour l' Sort
opé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.
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.
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)
où X
est 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.