Dans l'extrait de plan de requête suivant, il semble évident que l'estimation de la rangée pour l'opérateur Concatenation
devrait être ~4.3 billion rows
, ou la somme des estimations de la rangée pour ses deux entrées.
Cependant, une estimation de~238 million rows
est produit, conduisant à un sous-optimal Sort
/Stream Aggregate
Stratégie qui renversait des centaines de Go de données sur TEMPDB. Une estimation logiquement cohérente dans ce cas aurait produit un Hash Aggregate
, supprimé le déversement et améliore considérablement la performance de la requête.
Est-ce un bogue dans SQL Server 2014? Existe-t-il des circonstances valables dans lesquelles une estimation inférieure à celle des intrants pourrait être raisonnable? Quelles solutions de contournement pourraient être disponibles?
Voici le plan de requête complète (anonymisé). Je n'ai pas d'accès Sysadmin à ce serveur afin de fournir des sorties de QUERYTRACEON 2363
ou des drapeaux de trace similaires, mais peuvent être capables d'obtenir ces sorties d'un administrateur si elles seraient utiles.
La base de données est dans le niveau de compatibilité 120 et utilise donc le nouvel estimateur de cardinalité SQL Server 2014.
Les statistiques sont mises à jour manuellement chaque fois que les données sont chargées. Compte tenu du volume de données, nous utilisons actuellement la fréquence d'échantillonnage par défaut. Il est possible qu'un taux d'échantillonnage plus élevé (ou FULLSCAN
) puisse avoir un impact.
Pour citer Campbell Fraser sur cet élément de connexion :
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é perforation sur un sous-arbre de sous-arbre différemment structuré mais logiquement équivalent. En raison de la nature statistique de l'estimation de la cardinalité, l'estimation de différents arbres équivalents logiquement équivalents n'est pas garantie d'obtenir la même estimation. Donc, globalement, aucune garantie de cohérence attendue n'est fournie.
Pour développer cela un peu: la façon dont j'aime expliquer, c'est dire que le Initial L'estimation de la cardinalité (effectuée avant que les démarrages d'optimisation basés sur les coûts) produisent Des estimations de cardinalité plus "cohérentes", étant donné que l'arborescence initiale complète est traitée, chaque estimation ultérieure en fonction de la précédente.
Lors de l'optimisation des 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 desquelles peut nécessite une nouvelle Estimation de la cardinalité. Il n'y a pas de moyen général de dire quelle estimation sera généralement meilleure qu'une autre, il est donc tout à fait possible de finir par un plan final qui semble "incompatible". Ceci est simplement le résultat de la couture de "bits de plans" pour former l'arrangement final.
Tout ce qui a dit, il y avait des modifications détaillées dans le nouvel estimateur de Cardinalité (CE) introduite dans SQL Server 2014 qui rend cela quelque peu moins commun que le cas avec le CE d'origine.
Outre la mise à jour de la mise à jour cumulative la plus récente et vérifiant que les corrections d'optimisation avec 4199 sont activées, vos options principales sont d'essayer des modifications de statistiques/index (notant les avertissements des index manquants) et des 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 congelé avec un guide de plan, par exemple.
Le plan anonymisé permet d'évaluer le détail, mais j'éignerais également soigneusement les bitmaps pour voir s'ils sont de la variété "optimisée" (opt_bitmap) ou post-optimisation (bitmap). Je me méfie aussi des filtres.
Si la rangée compte, cela ressemble à une précision, cela semble être une requête pouvant bénéficier d'une colonne. Outre les avantages habituels, vous pourrez peut-être profiter de la subvention de mémoire dynamique pour les opérateurs de mode de lot ( drapeau de trace 9389 peut être nécessaire).
Construire un lit de test certes assez simple sur SQL Server 2012 (11.0.6020) me permet de recréer un plan avec deux requêtes assorties de hachage en cours de concaténité via un UNION ALL
. Mon lit de test n'affiche pas l'estimation incorrecte que vous voyez. Peut-être que ceci est Un problème SQL Server 2014 CE.
Je reçois une estimation de 133,785 rangées pour une requête qui retourne en réalité 280 lignes, cependant à s'attendre à ce que nous verrons plus loin sur:
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;
I Pensez La raison est autour du manque de statistiques pour les deux jointures résultantes qui sont syndiquées. SQL Server doit faire des suppositions éduquées dans la plupart des cas autour de la sélectivité des colonnes face à l'absence de statistiques.
Joe Sack a une lecture intéressante sur celle-ci ici .
Pour un UNION ALL
, Il est prudent de dire que nous verrons exactement le nombre total de lignes retournées par chaque composant de l'Union, cependant depuis que SQL Server utilise la ligne estimations pour le Deux composants du UNION ALL
, nous voyons qu'il ajoute le total estimé rangées des deux requêtes pour proposer l'estimation de l'opérateur de concaténation.
Dans mon exemple ci-dessus, le nombre estimé de lignes pour chaque partie du UNION ALL
Est de 66.8927, qui, lorsqu'il est résumé égale à 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 de l'Union ci-dessus ressemble à:
Vous pouvez voir le nombre de lignes "estimé" vs "réel". Dans mon cas, l'ajout du nombre "estimé" de lignes renvoyées par les deux opérateurs de match de hachage est exactement égal au montant indiqué par l'opérateur de concaténation.
J'essaierais d'obtenir la sortie de la trace 2363, etc., comme recommandé dans la poste de Paul White, vous voyez dans votre question. Alternativement, vous pouvez essayer d'utiliser OPTION (QUERYTRACEON 9481)
dans la requête à revenir à la version 70 CE pour voir si cela "corrige" le problème.