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 location
et testruns
est extrêmement différent entre les deux plans.
Rejoignez les estimations du plan: 1
Estimations du plan de sous-requête: 8 748
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 location
et 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,748
lignes seront renvoyées ( 26244/3
)
declare @i int
SELECT *
FROM testruns AS tr
WHERE tr.location_id = @i
Étant donné que le tableau locations
ne 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' )