Pourquoi la sous-requête utilise-t-elle le parallélisme et la jointure non?

16

Pourquoi le serveur SQL utilise-t-il le parallélisme lors de l'exécution de cette requête qui utilise une sous-requête mais pas lors de l'utilisation d'une jointure? La version join fonctionne en série et prend environ 30 fois plus de temps.

Rejoindre la version: ~ 30 secondes

entrez la description de l'image ici

Version de la sous-requête: <1 seconde

entrez la description de l'image ici

EDIT: versions XML du plan de requête:

Version JOIN

Version SUBQUERY

Chris L
la source

Réponses:

12

Comme déjà indiqué dans les commentaires, il semble que vous deviez mettre à jour vos statistiques.

Le nombre estimé de lignes sortant de la jointure entre locationet testrunsest extrêmement différent entre les deux plans.

Rejoignez les estimations du plan: 1

Plan 1

Estimations du plan de sous-requête: 8 748

entrez la description de l'image ici

Le nombre réel de lignes sortant de la jointure est de 14 276.

Bien sûr, cela n'a absolument aucun sens intuitif que la version de jointure devrait estimer que 3 lignes devraient provenir locationet produire une seule ligne jointe alors que la sous-requête estime qu'une seule de ces lignes produira 8 748 à partir de la même jointure mais néanmoins j'ai pu pour le reproduire.

Cela semble se produire s'il n'y a pas de croisement entre les histogrammes lors de la création des statistiques. La version join prend une seule ligne. Et la recherche d'égalité unique de la sous-requête suppose les mêmes lignes estimées qu'une recherche d'égalité contre une variable inconnue.

La cardinalité des tests est 26244. En supposant qu'il est rempli avec trois identifiants d'emplacement distincts, la requête suivante estime que les 8,748lignes seront renvoyées ( 26244/3)

declare @i int

SELECT *
FROM   testruns AS tr
WHERE  tr.location_id = @i

Étant donné que le tableau locationsne contient que 3 lignes, il est facile (si nous supposons qu'il n'y a pas de clés étrangères) de créer une situation dans laquelle les statistiques sont créées, puis les données sont modifiées de manière à affecter considérablement le nombre réel de lignes renvoyées, mais sont insuffisantes pour déclencher la mise à jour automatique des statistiques et le seuil de recompilation.

Comme SQL Server obtient le nombre de lignes qui sortent de cette jointure, toutes les autres estimations de lignes du plan de jointure sont faussement sous-estimées. En plus de signifier que vous obtenez un plan série, la requête obtient également une allocation de mémoire insuffisante et les sortes et les jointures de hachage se déversent tempdb.

Un scénario possible qui reproduit les lignes réelles et estimées affichées dans votre plan est ci-dessous.

CREATE TABLE location
  (
     id       INT CONSTRAINT locationpk PRIMARY KEY,
     location VARCHAR(MAX) /*From the separate filter think you are using max?*/
  )

/*Temporary ids these will be updated later*/
INSERT INTO location
VALUES      (101, 'Coventry'),
            (102, 'Nottingham'),
            (103, 'Derby')

CREATE TABLE testruns
  (
     location_id INT
  )

CREATE CLUSTERED INDEX IX ON testruns(location_id)

/*Add in 26244 rows of data split over three ids*/
INSERT INTO testruns
SELECT TOP (5984) 1
FROM   master..spt_values v1, master..spt_values v2
UNION ALL
SELECT TOP (5984) 2
FROM   master..spt_values v1, master..spt_values v2
UNION ALL
SELECT TOP (14276) 3
FROM   master..spt_values v1, master..spt_values v2

/*Create statistics. The location_id histograms don't intersect at all*/
UPDATE STATISTICS location(locationpk) WITH FULLSCAN;    
UPDATE STATISTICS testruns(IX) WITH FULLSCAN;

/* UPDATE location.id. Three row update is below recompile threshold*/
UPDATE location
SET    id = id - 100

Ensuite, l'exécution des requêtes suivantes donne la même différence estimée par rapport à la différence réelle

SELECT *
FROM   testruns AS tr
WHERE  tr.location_id = (SELECT id
                         FROM   location
                         WHERE  location = 'Derby')

SELECT *
FROM   testruns AS tr
       JOIN location loc
         ON tr.location_id = loc.id
WHERE  loc.location = ( 'Derby' ) 
Martin Smith
la source
Si une contrainte unique est ajoutée à l'emplacement, il devient évident que "=" renverra exactement une ligne. Ensuite, dans votre exemple, les plans de requête deviennent identiques (analyses -> recherche): alter table Location add constraint U_Location_Location unique nonclustered (Location);
crokusek
@crokusek oui. Réalisé ce que vous vouliez dire par la suite et supprimé mon commentaire! Cela augmente-t-il également le nombre estimé de lignes pour la version de jointure comme la sous-requête? Pas sur PC pour le moment?
Martin Smith
@crokusek Yep. ressemble aux mêmes lignes estimées de la jointure que pour la sous-requête dans ce cas singleton.
Martin Smith
Oui. Plan de requête identique, les deux estimations 8748, les deux réels 14276. Au fait, je pensais que pré-calculer l'emplacementId résoudrait cette différence, mais ce n'est pas le cas.
crokusek
1
@crokusek - J'ajouterai également la contrainte unique à l'emplacement et à d'autres endroits similaires dans ma base de données. Je dois admettre que je ne savais pas que cela affectait l'optimisation des requêtes. Je pensais que c'était juste pour assurer l'intégrité des données. Merci pour votre contribution sur cette question.
Chris L