Dans les requêtes ci-dessous, on estime que 1 000 plans d'exécution sont estimés à un indice unique.
Les recherches sont entraînées par une analyse ordonnée sur la même table source, ce qui semble donc finir par chercher les mêmes valeurs dans le même ordre.
Les deux boucles imbriquées ont <NestedLoops Optimized="false" WithOrderedPrefetch="true">
Quelqu'un sache pourquoi cette tâche est calculée au 0,172434 dans le premier plan, mais 3.01702 dans la seconde?
(La raison de la question est que la première requête m'a été suggérée comme une optimisation due au coût de plan considérable beaucoup plus faible. Cela me semble en fait comme si cela fonctionne plus, mais je tente juste d'expliquer la divergence .. .)
CREATE TABLE dbo.Target(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL);
CREATE TABLE dbo.Staging(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL);
INSERT INTO dbo.Target
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1,
master..spt_values v2;
INSERT INTO dbo.Staging
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1;
WITH T
AS (SELECT *
FROM Target AS T
WHERE T.KeyCol IN (SELECT S.KeyCol
FROM Staging AS S))
MERGE T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
INSERT ( KeyCol, OtherCol )
VALUES(S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
UPDATE SET T.OtherCol = S.OtherCol;
MERGE Target T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
INSERT ( KeyCol, OtherCol )
VALUES( S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
UPDATE SET T.OtherCol = S.OtherCol;
Ce qui précède a été testé sur SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64)
@ Joe'Blovybish souligne dans les commentaires qu'un reprovement plus simple serait
SELECT *
FROM staging AS S
LEFT OUTER JOIN Target AS T
ON T.KeyCol = S.KeyCol;
vs
SELECT *
FROM staging AS S
LEFT OUTER JOIN (SELECT * FROM Target) AS T
ON T.KeyCol = S.KeyCol;
Pour la table de stadification de 1 000 rangées, les deux ci -form ont toujours la même forme de plan avec des boucles imbriquées et le plan sans la table dérivée apparaissant moins cher, mais pour un 10 000 étagères de rangée et la même table cible que ci-dessus, la différence de coûts change la forme du plan (avec une scanne complète et une jonction à la fusion semblant relativement plus attrayante que la recherche de coûts de manière expessive) montrant que cette écart de coût peut avoir des conséquences autres que de rendre plus difficiles à faire comparer les plans.
Quelqu'un sache pourquoi cette tâche est calculée au 0,172434 dans le premier plan, mais 3.01702 dans la seconde?
De manière générale, un côté intérieur cherche en dessous d'une jointure de boucles imbriqués est de chiffrement en supposant un motif d'E/S aléatoire. Il existe une simple réduction de remplacement pour les accès ultérieurs, en tenant compte des chances que la page requise ait déjà été mise en mémoire par une itération précédente. Cette évaluation de base produit le coût standard (supérieur).
Il existe une autre contribution de coûts, Smart Rechercher des coûts, sur quel peu de détails sont connus. Mon devinez (et c'est tout ce qu'il est à ce stade) est que SSC tente d'évaluer la recherche de la recherche interne des E/S Coût plus en détail, peut-être en considérant la commande locale et/ou la gamme de valeurs à récupérer. Qui sait.
Par exemple, la première opération de recherche apporte non seulement la ligne demandée, mais toutes les lignes de cette page (dans l'ordre d'index). Compte tenu du modèle d'accès global, la récupération des 1000 lignes de 1000 cherches ne nécessite que 2 lectures physiques, même avec une lecture en lecture et une préfettration désactivée. À partir de ce point de vue, le coût d'E/S par défaut représente une surestimation importante et le coût ajusté SSC est plus proche de la réalité.
Il semble raisonnable de s'attendre à ce que SSC soit plus efficace lorsque la boucle entraîne un indice de recherche plus ou moins directement, et la référence extérieure de JOINT est la base de l'opération de recherche. De ce que je peux dire, SSC est toujours tenté d'opérations physiques appropriées, mais ne produit le plus souvent aucun ajustement à la baisse lorsque la recherche est séparée de la jointure par d'autres opérations. Les filtres simples sont une exception à cet égard, peut-être parce que SQL Server peut souvent les pousser dans l'opérateur d'accès aux données. Dans tous les cas, l'optimiseur a un support assez profond pour les sélections.
Il est regrettable que le scalaire de calcul pour les projections extérieures de sous-requête semble interférer avec SSC ici. Les scalaires de calcul sont généralement déplacés au-dessus de la jointure, mais ceux-ci doivent rester là où ils sont. Malgré tout, la plupart des scalaires normaux de calcul sont assez transparents à l'optimisation, c'est donc un peu surprenant.
Peu importe, lorsque le fonctionnement physique PhyOp_Range
est produit à partir d'une sélection simple sur un index SelIdxToRng
, SSC est efficace. Lorsque le plus complexe SelToIdxStrategy
(sélection sur une table à une stratégie d'index) est employé, le PhyOp_Range
exécute SSC mais n'entraîne aucune réduction. Encore une fois, il semble que des opérations plus simples et plus directes fonctionnent mieux avec SSC.
J'aimerais pouvoir vous dire exactement ce que SSC fait et montrer les calculs exacts, mais je ne connais pas ces détails. Si vous souhaitez explorer la sortie de trace limitée disponible pour vous-même, vous pouvez utiliser un indicateur de trace non documenté 2398. Un exemple de sortie est:
Cherche intelligente Costing (7.1) :: 1.34078E + 154, 0,001
Cet exemple concerne le groupe 7, alternative 1, montrant une limite supérieure de coûts et un facteur de 0,001. Pour voir des facteurs plus propres, assurez-vous de reconstruire les tables sans parallélisme afin que les pages soient aussi denses que possible. Sans cela, le facteur est plus comme 0,000821 pour votre exemple de table cible. Il existe des relations assez évidentes là-bas, bien sûr.
SSC peut également être désactivé avec un drapeau de trace non documenté 2399. Avec cet indicateur actif, les deux coûts sont la valeur la plus élevée.
Pas sûr que c'est une réponse, mais c'est un peu long pour un commentaire. La cause de la différence est la spéculation pure de ma part et peut-être peut-être être de la nourriture à penser à d'autres.
Quères simplifiées avec des plans d'exécution.
SELECT S.KeyCol,
S.OtherCol,
T.*
FROM staging AS S
LEFT OUTER JOIN Target AS T
ON T.KeyCol = S.KeyCol;
SELECT S.KeyCol,
S.OtherCol,
T.*
FROM staging AS S
LEFT OUTER JOIN (
SELECT *
FROM Target
) AS T
ON T.KeyCol = S.KeyCol;
La principale différence entre ces requêtes équivalentes qui pourrait vraiment entraîner des plans d'exécution identiques est l'opérateur scalaire de calcul. Je ne sais pas pourquoi il doit être là, mais je suppose que c'est aussi loin que l'optimiseur peut aller optimiser la table dérivée.
Je suppose que la présence du scalaire Compute est ce qui est en train de choisir le IO Coût de la deuxième requête.
De à l'intérieur de l'optimiseur: planifier les coûts
Le coût de la CPU est calculé comme 0,0001581 pour la première rangée et 0.000011 pour les lignes suivantes.
...
[.____] Le coût d'E/S de 0,003125 est exactement 1/320 - reflétant l'hypothèse du modèle que le sous-système de disque peut effectuer 320 opérations aléatoires d'E/S par seconde
...
[.____] Le composant des coûts est suffisamment intelligent pour reconnaître que le nombre total de pages à apporter à partir du disque ne peut jamais dépasser le nombre de pages requises pour stocker toute la table.
Dans mon cas, la table prend 5618 pages et obtenir 1000 lignes de 1000000 lignes, le nombre estimé de pages requis est de 5,618 donnant le IO = coût de 0,015625.
Le coût de la CPU pour les deux crises de requêtes est la même, 0.0001581 * 1000 executions = 0.1581
.
Donc, selon l'article lié ci-dessus, nous pouvons calculer le coût de la première requête à être 0.173725.
Et en supposant que je suis correct sur la manière dont le scalaire Compute fait un gâchis de IO Coût peut être calculé à 3.2831.
Ce n'est pas exactement ce qui est montré dans les plans, mais il est juste là dans le quartier.
Ce n'est pas vraiment une réponse non plus - comme l'a noté Mikael, il est difficile de discuter de ce problème dans les commentaires ...
Fait intéressant, si vous convertissez la sous-requête (select KeyCol FROM Target)
Dans une TVF en ligne, vous voyez le plan et ses coûts, sont identiques à la simple requête originale:
CREATE FUNCTION dbo.cs_test()
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (
SELECT KeyCol FROM dbo.Target
);
/* "normal" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol
FROM staging AS S
LEFT OUTER JOIN Target AS T ON T.KeyCol = S.KeyCol;
/* "subquery" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol
FROM staging AS S
LEFT OUTER JOIN (SELECT KeyCol FROM Target) AS T ON T.KeyCol = S.KeyCol;
/* "inline-TVF" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol
FROM staging AS S
LEFT OUTER JOIN dbo.cs_test() t ON s.KeyCol = t.Keycol
Les plans de requête ( Lien pastheplan ):
La déduction me conduit à croire que le moteur des coûts est confus à propos de impact potentiel que ce type de sous-requête peut avoir .
Prenez par exemple, ce qui suit:
SELECT S.KeyCol, s.OtherCol, T.KeyCol
FROM staging AS S
LEFT OUTER JOIN (
SELECT KeyCol = CHECKSUM(NEWID())
FROM Target
) AS T ON T.KeyCol = S.KeyCol;
Comment vous coûte cela? L'optimiseur de requête choisit un plan très similaire à la variante "sous-requête" ci-dessus, contenant un scalaire de calcul ( Link PasthePlan.com ):
Le scalaire Compute a un coût assez différent de la variante "sous-requête" présentée ci-dessus, mais il est encore simplement de penser que l'optimiseur de requête n'a aucun moyen de savoir, a priori, ce que le nombre de rangées retournées pourrait être. Le plan utilise une correspondance de hachage pour la jointure extérieure gauche car les estimations de la ligne sont inconnaissables et définies par conséquent sur le nombre de lignes dans la table cible.
Je ne suis pas une excellente conclusion de cela, sauf que je suis d'accord avec le travail Mikael dans sa réponse et je suis plein d'espoir que quelqu'un d'autre peut venir avec une meilleure réponse.