Pourquoi l'opérateur de concaténation estime-t-il moins de lignes que ses entrées?

20

Dans l'extrait de plan de requête suivant, il semble évident que l'estimation de ligne pour l' Concatenationopérateur doit être ~4.3 billion rows, ou la somme des estimations de ligne pour ses deux entrées.

Cependant, une estimation de ~238 million rowsest produite, conduisant à une stratégie Sort/ sous-optimale Stream Aggregatequi répand des centaines de Go de données à tempdb. Une estimation logiquement cohérente dans ce cas aurait produit un Hash Aggregate, supprimé le déversement et amélioré considérablement les performances des requêtes.

Est-ce un bogue dans SQL Server 2014? Existe-t-il des circonstances valables dans lesquelles une estimation inférieure aux intrants pourrait être raisonnable? Quelles solutions de contournement pourraient être disponibles?

entrez la description de l'image ici

Voici le plan de requête complet (anonymisé). Je n'ai pas d'accès administrateur à ce serveur afin de fournir des sorties à partir de QUERYTRACEON 2363ou des indicateurs de trace similaires, mais je peux être en mesure d'obtenir ces sorties d'un administrateur si cela peut être utile.

La base de données est au niveau de compatibilité 120 et utilise donc le nouvel estimateur de cardinalité SQL Server 2014.

Les statistiques sont mises à jour manuellement chaque fois que des données sont chargées. Compte tenu du volume de données, nous utilisons actuellement le taux d'échantillonnage par défaut. Il est possible qu'un taux d'échantillonnage plus élevé (ou FULLSCAN) ait un impact.

Geoff Patterson
la source

Réponses:

21

Pour citer Campbell Fraser sur cet article Connect :

Ces "incohérences de cardinalité" peuvent survenir dans un certain nombre de situations, y compris lorsque concat est utilisé. Ils peuvent survenir parce que l'estimation d'un sous-arbre particulier dans le plan final peut avoir été effectuée sur un sous-arbre différemment structuré mais logiquement équivalent. En raison de la nature statistique de l'estimation de la cardinalité, l'estimation sur des arbres différents mais logiquement équivalents n'est pas garantie d'obtenir la même estimation. Donc, dans l'ensemble, aucune garantie de cohérence attendue n'est fournie.

Pour développer un peu cela: La façon dont j'aime l'expliquer est de dire que l' estimation de cardinalité initiale (effectuée avant le début de l'optimisation basée sur les coûts) produit des estimations de cardinalité plus "cohérentes", puisque tout l'arbre initial est traité, avec chaque estimation dépendant directement de la précédente.

Pendant l'optimisation basée sur les coûts, des parties de l'arborescence du plan (un ou plusieurs opérateurs) peuvent être explorées et remplacées par des alternatives, chacune pouvant nécessiter une nouvelle estimation de cardinalité. Il n'existe aucun moyen général de dire quelle estimation sera généralement meilleure qu'une autre, il est donc tout à fait possible de se retrouver avec un plan final qui semble "incohérent". C'est simplement le résultat de l'assemblage de «morceaux de plans» pour former l'arrangement final.

Cela dit, le nouvel estimateur de cardinalité (CE) introduit dans SQL Server 2014 a été légèrement modifié, ce qui le rend un peu moins courant que dans le cas du CE d'origine.

Outre la mise à niveau vers la dernière mise à jour cumulative et la vérification de l'activation des correctifs de l'optimiseur avec 4199, vos principales options sont d'essayer les modifications des statistiques / index (en notant les avertissements pour les index manquants) et les mises à jour, ou d'exprimer la requête différemment. L'objectif étant d'acquérir un plan qui affiche le comportement dont vous avez besoin. Cela peut ensuite être gelé avec un guide de plan, par exemple.

Le plan anonymisé rend difficile l'évaluation des détails, mais je regarderais également attentivement les bitmaps pour voir s'ils sont de la variété `` optimisée '' (Opt_Bitmap) ou post-optimisée (Bitmap). Je me méfie également des filtres.

Si le nombre de lignes est quelque chose de précis, cela ressemble à une requête qui pourrait bénéficier de columnstore. Outre les avantages habituels, vous pourrez peut-être profiter de l'allocation de mémoire dynamique pour les opérateurs en mode batch (l' indicateur de trace 9389 peut être requis).

Paul White dit GoFundMonica
la source
7

Construire un banc d'essai assez simple sur SQL Server 2012 (11.0.6020) me permet de recréer un plan avec deux requêtes de hachage concaténées via a UNION ALL. Mon banc d'essai n'affiche pas l'estimation incorrecte que vous voyez. Peut-être est un problème de CE SQL Server 2014.

J'obtiens une estimation de 133,785 lignes pour une requête qui renvoie en fait 280 lignes, mais cela est normal, comme nous le verrons plus loin:

IF OBJECT_ID('dbo.Union1') IS NOT NULL
DROP TABLE dbo.Union1;
CREATE TABLE dbo.Union1
(
    Union1_ID INT NOT NULL
        CONSTRAINT PK_Union1
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , Union1_Text VARCHAR(255) NOT NULL
    , Union1_ObjectID INT NOT NULL
);

IF OBJECT_ID('dbo.Union2') IS NOT NULL
DROP TABLE dbo.Union2;
CREATE TABLE dbo.Union2
(
    Union2_ID INT NOT NULL
        CONSTRAINT PK_Union2
        PRIMARY KEY CLUSTERED
        IDENTITY(2,2)
    , Union2_Text VARCHAR(255) NOT NULL
    , Union2_ObjectID INT NOT NULL
);

INSERT INTO dbo.Union1 (Union1_Text, Union1_ObjectID)
SELECT o.name, o.object_id
FROM sys.objects o;

INSERT INTO dbo.Union2 (Union2_Text, Union2_ObjectID)
SELECT o.name, o.object_id
FROM sys.objects o;
GO

SELECT *
FROM dbo.Union1 u1
    INNER HASH JOIN sys.objects o ON u1.Union1_ObjectID = o.object_id
UNION ALL
SELECT *
FROM dbo.Union2 u2
    INNER HASH JOIN sys.objects o ON u2.Union2_ObjectID = o.object_id;

Je pense que la raison réside dans le manque de statistiques pour les deux jointures résultantes qui sont UNIONed. SQL Server doit faire des suppositions éclairées dans la plupart des cas concernant la sélectivité des colonnes face au manque de statistiques.

Joe Sack a une lecture intéressante à ce sujet ici .

Pour un UNION ALL, il est sûr de dire que nous verrons exactement le nombre total de lignes renvoyées par chaque composant de l'union, mais puisque SQL Server utilise des estimations de ligne pour les deux composants de UNION ALL, nous voyons qu'il ajoute le total estimé des lignes des deux requêtes pour arriver à l'estimation de l'opérateur de concaténation.

Dans mon exemple ci-dessus, le nombre estimé de lignes pour chaque partie de la UNION ALLest de 66,8927, qui, lorsqu'il est additionné, est égal à 133,785, que nous voyons pour le nombre estimé de lignes pour l'opérateur de concaténation.

Le plan d'exécution réel pour la requête d'union ci-dessus ressemble à:

entrez la description de l'image ici

Vous pouvez voir le nombre "estimé" par rapport au nombre "réel" de lignes. Dans mon cas, l'ajout du nombre "estimé" de lignes renvoyées par les deux opérateurs de correspondance de hachage est exactement égal au montant affiché par l'opérateur de concaténation.

J'essaierais d'obtenir la sortie de la trace 2363 etc. comme recommandé dans le post de Paul White que vous montrez dans votre question. Vous pouvez également essayer d'utiliser OPTION (QUERYTRACEON 9481)dans la requête pour revenir à la version 70 CE pour voir si cela «résout» le problème.

Max Vernon
la source
1
Merci. J'ai certainement vu que la "raison du manque de statistiques pour les deux jointures résultantes qui sont UNIONnées" a un grand impact sur les jointures ou agrégations ultérieures (qui se produisent après l'UNION). SQL 2014 gère en fait mieux que SQL 2012 d'après mon expérience. Voici un script de test simple que j'ai utilisé dans le passé par exemple: gist.github.com/anonymous/1497112d8b25ab8fb782a04569959c68 Cependant, je ne pense pas qu'un opérateur de concaténation aurait besoin du même type d'informations sur la distribution des valeurs qu'une jointure va peut-être avoir besoin de.
Geoff Patterson
Je suis d'accord avec vous que la concaténation ne devrait pas avoir besoin de statistiques pour fonctionner correctement. Il devrait simplement être en mesure d'ajouter de manière fiable les estimations de lignes entrantes pour avoir une bonne idée du nombre de lignes qu'il produira. Comme @PaulWhite le montre dans sa réponse, ce n'est étonnamment pas toujours le cas. Pour moi, le point à retenir ici est que cela peut sembler simple, mais en réalité, ce n'est peut-être pas le cas. Je suis vraiment content que vous ayez posé la question comme vous l'avez fait, je souhaite seulement que vous n'ayez pas à anonymiser le plan - il aurait été intéressant de voir la requête réelle.
Max Vernon