Comment améliorer les estimations de lignes afin de réduire les risques de déversements à tempdb

11

Je remarque que lorsqu'il y a un déversement d'événements tempdb (provoquant des requêtes lentes), les estimations de ligne sont souvent éloignées pour une jointure particulière. J'ai vu des événements de déversement se produire avec des jointures de fusion et de hachage et ils augmentent souvent la durée d'exécution de 3 à 10 fois. Cette question concerne la façon d'améliorer les estimations des rangées en supposant que cela réduira les risques d'événements de déversement.

Nombre réel de lignes 40k.

Pour cette requête, le plan affiche une estimation de ligne incorrecte (11,3 lignes):

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

Pour cette requête, le plan affiche une bonne estimation de ligne (56 000 lignes):

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

Des statistiques ou des conseils peuvent-ils être ajoutés pour améliorer les estimations de ligne pour le premier cas? J'ai essayé d'ajouter des statistiques avec des valeurs de filtre particulières (propriété = 2840) mais soit je n'ai pas pu obtenir la combinaison correcte, soit elle est peut-être ignorée car l'ObjectId est inconnu au moment de la compilation et il se peut qu'il choisisse une moyenne parmi tous les ObjectIds.

Existe-t-il un mode dans lequel il ferait d'abord la requête de sonde, puis l'utiliserait pour déterminer les estimations de ligne ou doit-il voler à l'aveugle?

Cette propriété particulière a de nombreuses valeurs (40k) sur quelques objets et zéro sur la grande majorité. Je serais heureux d'avoir une indication où le nombre maximum de lignes attendues pour une jointure donnée pourrait être spécifié. Il s'agit d'un problème généralement obsédant car certains paramètres peuvent être déterminés dynamiquement dans le cadre de la jointure ou seraient mieux placés dans une vue (pas de prise en charge des variables).

Y a-t-il des paramètres qui peuvent être ajustés pour minimiser les risques de déversements sur tempdb (par exemple, minimum de mémoire par requête)? Un plan robuste n'a eu aucun effet sur l'estimation.

Edit 2013.11.06 : Réponse aux commentaires et informations supplémentaires:

Voici les images du plan de requête. Les avertissements concernent le prédicat de cardinalité / recherche avec convert ():

entrez la description de l'image ici entrez la description de l'image ici

Selon le commentaire de @Aaron Bertrand, j'ai essayé de remplacer convert () comme test:

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

entrez la description de l'image ici

En tant que point d'intérêt étrange mais réussi, cela lui a également permis de court-circuiter la recherche:

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

entrez la description de l'image ici

Les deux répertorient une recherche de clé appropriée, mais seules les premières répertorient une "sortie" d'ObjectId. Je suppose que cela indique que le second est en effet un court-circuit?

Quelqu'un peut-il vérifier si des sondes sur une seule ligne ont déjà été effectuées pour aider à l'estimation des lignes? Il semble erroné de limiter l'optimisation aux seules estimations d'histogramme lorsqu'une recherche PK à une seule ligne peut améliorer considérablement la précision de la recherche dans l'histogramme (surtout s'il y a un potentiel de déversement ou un historique). Lorsqu'il y a 10 de ces sous-jointures dans une requête réelle, idéalement, elles se produiraient en parallèle.

Une note latérale, puisque sql_variant stocke son type de base (SQL_VARIANT_PROPERTY = BaseType) dans le champ lui-même, je m'attendrais à ce qu'un convert () soit presque sans coût tant qu'il est "directement" convertible (par exemple, pas de chaîne en décimale mais plutôt d'int int ou peut-être int à bigint). Comme cela n'est pas connu au moment de la compilation mais peut être connu de l'utilisateur, peut-être qu'une fonction "AssumeType (type, ...)" pour sql_variants permettrait de les traiter de manière plus transparente.

crokusek
la source
1
Une première supposition serait que la conversion en bigint rejette vos estimations (le plan de requête aura un avertissement à ce sujet dans SQL Server 2012) mais d'un autre côté, votre sous-requête ne pourra jamais renvoyer autre chose que 0 ou 1 lignes pour une requête réussie. Il serait intéressant de voir les plans de requête que vous avez. Peut-être comme un lien vers la version XML.
Mikael Eriksson
2
Que gagnez-vous à avoir la sous-requête en ligne? Je dirais que le retirer séparément est plus clair dans l'ensemble, et puisqu'il conduit à de meilleures estimations, pourquoi ne pas simplement utiliser cette méthode?
Aaron Bertrand
2
Quelle version de SQL Server? Pouvez-vous fournir un DDL de table et d'index et des blobs de statistiques (une et plusieurs colonnes) pour les tables afin que nous puissions voir les détails du problème? Fractionner la requête en utilisant ce declare @a bigint = que vous avez fait me semble une solution naturelle, pourquoi est-ce inacceptable?
Paul White 9
2
Je suppose que votre conception est une conception EAV (très simpliste) qui vous oblige à utiliser des CONVERT()colonnes, puis à les joindre. Ce n'est certainement pas efficace dans la plupart des cas. Dans celui-ci, ce n'est qu'une seule valeur à convertir, ce n'est probablement pas un problème, mais quels index avez-vous sur la table? Les conceptions EAV fonctionnent généralement bien, uniquement avec une indexation appropriée (ce qui signifie beaucoup d'index dans les tableaux généralement étroits).
ypercubeᵀᴹ
@Paul White, en ce qui concerne la séparation ... c'est bien comme solution pour ce cas. Mais pour plus général / complexe, je ne veux surtout pas abandonner la parallélisation et la lisibilité. Supposons que j'en ai 10 en tant que sous-requêtes (certaines étant plus complexes) dans une requête, mais seulement 5 doivent être "mûres" avant que le reste de la requête puisse commencer - j'aimerais éviter d'avoir à déterminer quelles sont 5 d'entre elles.
crokusek

Réponses:

7

Je ne commenterai pas les déversements, tempdb ou les indices, car la requête semble assez simple à prendre en compte. Je pense que l'optimiseur de SQL-Server fera très bien son travail, s'il existe des index adaptés à la requête.

Et votre fractionnement en deux requêtes est bon car il montre quels index seront utiles. La première partie:

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

a besoin d'un index sur l' (PropertyId, ObjectId, Sequence)inclusion du Value. Je ferais en sorte UNIQUEd'être en sécurité. La requête génèrerait quand même une erreur pendant l'exécution si plusieurs lignes étaient retournées, il est donc bon de s'assurer à l'avance que cela ne se produira pas, avec l'index unique:

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

La deuxième partie de la requête:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

a besoin d'un index pour (PropertyId, ObjectId)inclure Value:

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

Si l'efficacité n'est pas améliorée ou si ces indices n'ont pas été utilisés ou s'il y a encore des différences dans les estimations de lignes qui apparaissent, il serait alors nécessaire d'approfondir cette requête.

Dans ce cas, les conversions (nécessaires de la conception de l'EAV et le stockage de différents types de données dans les mêmes colonnes) sont une cause probable et votre solution de fractionnement (comme @AAron Bertrand et @Paul White commentent) la requête en deux parties semble naturelle et le chemin à parcourir. Une refonte afin d'avoir différents types de données dans leurs colonnes respectives pourrait en être une autre.

ypercubeᵀᴹ
la source
Les tableaux avaient des indices couvrant - j'aurais dû le dire dans la question. L'exemple est vraiment une sous-jointure alimentant une requête plus grande, c'est pourquoi il y a tout ce bruit sur les déversements de tempdb.
crokusek
5

En réponse partielle à la question explicite sur l'amélioration des statistiques ...

Notez que les estimations de ligne, même pour le cas séparé séparément, sont toujours éteintes de 10X (4k contre 40k attendues).

L'histogramme des statistiques était probablement trop mince pour cette propriété, car il s'agit d'un long tableau (vertical) de 3,5 millions de lignes et cette propriété particulière est extrêmement clairsemée.

Créez des statistiques supplémentaires (quelque peu redondantes avec les statistiques IX) pour la propriété clairsemée:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

Les originaux:

entrez la description de l'image ici entrez la description de l'image ici

Avec convert () supprimé (approprié):

entrez la description de l'image ici

Avec convert () supprimé (court-circuit):

entrez la description de l'image ici

Toujours éteint de ~ 2X probablement parce que> 99,9% des objets n'ont pas du tout de propriété 2840 définie. En fait, juste pour ce cas de test, la propriété existe uniquement sur 1 des 200 000 objets distincts de la table de lignes de 3,5 millions. C'est incroyable, ça a vraiment approché. Ajuster le filtre pour être moins ObjectIds,

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

Hmm, pas de changement ... Soutenu cela ajouté "avec analyse complète" à la fin des statistiques (peut-être pourquoi les deux précédents n'ont pas fonctionné) et oui:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

entrez la description de l'image ici

Yay. Ainsi, dans un tableau très vertical avec un IX couvrant largement, l'ajout de statistiques filtrées supplémentaires semble être une grande amélioration (notamment pour les combinaisons de touches clairsemées mais très variables).

crokusek
la source
Un lien vers certains problèmes problématiques avec les statistiques multi-colonnes.
crokusek