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 ():
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);
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);
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.
la source
declare @a bigint =
que vous avez fait me semble une solution naturelle, pourquoi est-ce inacceptable?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).Réponses:
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:
a besoin d'un index sur l'
(PropertyId, ObjectId, Sequence)
inclusion duValue
. Je ferais en sorteUNIQUE
d'ê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:La deuxième partie de la requête:
a besoin d'un index pour
(PropertyId, ObjectId)
inclureValue
: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.
la source
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:
Les originaux:
Avec convert () supprimé (approprié):
Avec convert () supprimé (court-circuit):
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,
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:
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).
la source